| rev | line source | 
| jbe/bsw@0 | 1 #!/usr/bin/env lua | 
| jbe/bsw@0 | 2 | 
| jbe/bsw@0 | 3 if not pcall( | 
| jbe/bsw@0 | 4   function() | 
| jbe/bsw@0 | 5     require "extos" | 
| jbe/bsw@0 | 6   end | 
| jbe/bsw@0 | 7 ) then | 
| jbe/bsw@0 | 8   io.stderr:write('Could not load library "extos".\n') | 
| jbe/bsw@0 | 9   io.stderr:write('Hint: Set LUA_CPATH="/path_to_extos_library/?.so;;"\n') | 
| jbe/bsw@0 | 10 end | 
| jbe/bsw@0 | 11 | 
| jbe/bsw@0 | 12 | 
| jbe/bsw@0 | 13 local args = {...} | 
| jbe/bsw@0 | 14 | 
| jbe/bsw@0 | 15 if #args == 0 then | 
| jbe/bsw@0 | 16   print() | 
| jbe/bsw@0 | 17   print("This program creates translation files by traversing source directories.") | 
| jbe/bsw@0 | 18   print() | 
| jbe/bsw@0 | 19   print("Two formats are supported: lua files and po files.") | 
| jbe/bsw@0 | 20   print("At runtime a lua file is needed.") | 
| jbe/bsw@0 | 21   print("For use with po-file editors you may want to create po files first though.") | 
| jbe/bsw@0 | 22   print() | 
| jbe/bsw@0 | 23   print("Create or update a lua file: langtool.lua dir1/ dir2/ ... <basename>.lua") | 
| jbe/bsw@0 | 24   print("Create or update a po file:  langtool.lua dir1/ dir2/ ... <basename>.po") | 
| jbe/bsw@0 | 25   print("Convert po file to lua file: langtool.lua <basename>.po <basename>.lua") | 
| jbe/bsw@0 | 26   print("Convert lua file to po file: langtool.lua <basename>.lua <basename>.po") | 
| jbe/bsw@0 | 27   print() | 
| jbe/bsw@0 | 28 end | 
| jbe/bsw@0 | 29 | 
| jbe/bsw@0 | 30 local in_filename, in_filetype, out_filename, out_filetype | 
| jbe/bsw@0 | 31 local directories = {} | 
| jbe/bsw@0 | 32 | 
| jbe/bsw@0 | 33 for arg_num, arg in ipairs(args) do | 
| jbe/bsw@0 | 34   local function arg_error(msg) | 
| jbe/bsw@0 | 35     error("Illegal command line argument #" .. arg_num .. ": " .. msg) | 
| jbe/bsw@0 | 36   end | 
| jbe/bsw@0 | 37   local po = string.match(arg, "^po:(.*)$") or string.match(arg, "^(.*%.po)$") | 
| jbe/bsw@0 | 38   local lua = string.match(arg, "^lua:(.*)$") or string.match(arg, "^(.*%.lua)$") | 
| jbe/bsw@0 | 39   local filetype | 
| jbe/bsw@0 | 40   if po and not lua then filetype = "po" | 
| jbe/bsw@0 | 41     filetype = "po" | 
| jbe/bsw@0 | 42   elseif lua and not po then filetype = "lua" | 
| jbe/bsw@0 | 43     filetype = "lua" | 
| jbe/bsw@0 | 44   else | 
| jbe/bsw@0 | 45     filetype = "path" | 
| jbe/bsw@0 | 46   end | 
| jbe/bsw@0 | 47   if filetype == "path" then | 
| jbe/bsw@0 | 48     table.insert(directories, arg) | 
| jbe/bsw@0 | 49   elseif filetype == "po" or filetype == "lua" then | 
| jbe/bsw@0 | 50     if not out_filename then | 
| jbe/bsw@0 | 51       out_filename = arg | 
| jbe/bsw@0 | 52       out_filetype = filetype | 
| jbe/bsw@0 | 53     elseif not in_filename then | 
| jbe/bsw@0 | 54       in_filename = out_filename | 
| jbe/bsw@0 | 55       in_filetype = out_filetype | 
| jbe/bsw@0 | 56       out_filename = arg | 
| jbe/bsw@0 | 57       out_filetype = filetype | 
| jbe/bsw@0 | 58     else | 
| jbe/bsw@0 | 59       arg_error("Only two language files (one input and one output file) can be specified.") | 
| jbe/bsw@0 | 60     end | 
| jbe/bsw@0 | 61   else | 
| jbe/bsw@0 | 62     -- should not happen, as default type is "path" | 
| jbe/bsw@0 | 63     arg_error("Type not recognized") | 
| jbe/bsw@0 | 64   end | 
| jbe/bsw@0 | 65 end | 
| jbe/bsw@0 | 66 | 
| jbe/bsw@0 | 67 if #directories > 0 and not os.listdir then | 
| jbe/bsw@0 | 68   io.stderr:write('Fatal: Cannot traverse directories without "extos" library -> Abort\n') | 
| jbe/bsw@0 | 69   os.exit(1) | 
| jbe/bsw@0 | 70 end | 
| jbe/bsw@0 | 71 | 
| jbe/bsw@0 | 72 if out_filename and not in_filename then | 
| jbe/bsw@0 | 73   local file = io.open(out_filename, "r") | 
| jbe/bsw@0 | 74   if file then | 
| jbe/bsw@0 | 75     in_filename = out_filename | 
| jbe/bsw@0 | 76     in_filetype = out_filetype | 
| jbe/bsw@0 | 77     file:close() | 
| jbe/bsw@0 | 78   end | 
| jbe/bsw@0 | 79 end | 
| jbe/bsw@0 | 80 | 
| jbe/bsw@0 | 81 local translations = { } | 
| jbe/bsw@0 | 82 | 
| jbe/bsw@0 | 83 local function traverse(path) | 
| jbe/bsw@0 | 84   local filenames = os.listdir(path) | 
| jbe/bsw@0 | 85   if not filenames then return false end | 
| jbe/bsw@0 | 86   for num, filename in ipairs(filenames) do | 
| jbe/bsw@0 | 87     if not string.find(filename, "^%.") then | 
| jbe/bsw@0 | 88       if string.find(filename, "%.lua$") then | 
| jbe/bsw@0 | 89         for line in io.lines(path .. "/" .. filename) do | 
| jbe/bsw@0 | 90           -- TODO: exact parsing of comments and escape characters | 
| jbe/bsw@0 | 91           for key in string.gmatch(line, "_%(?'([^'\]+)'") do | 
| jbe/bsw@0 | 92             if | 
| jbe/bsw@0 | 93               key ~= "([^" and | 
| jbe/bsw@0 | 94               (not string.find(key, "^%s*%.%.[^%.]")) and | 
| jbe/bsw@0 | 95               (not string.find(key, "^%s*,[^,]")) | 
| jbe/bsw@0 | 96             then | 
| jbe/bsw@4 | 97               local key = key:gsub("\\n", "\n") | 
| jbe/bsw@0 | 98               translations[key] = false | 
| jbe/bsw@0 | 99             end | 
| jbe/bsw@0 | 100           end | 
| jbe/bsw@0 | 101           for key in string.gmatch(line, '_%(?"([^"\]+)"') do | 
| jbe/bsw@0 | 102             if | 
| jbe/bsw@0 | 103               key ~= "([^" and | 
| jbe/bsw@0 | 104               (not string.find(key, "^%s*%.%.[^%.]")) and | 
| jbe/bsw@0 | 105               (not string.find(key, "^%s*,[^,]")) | 
| jbe/bsw@0 | 106             then | 
| jbe/bsw@4 | 107               local key = key:gsub("\\n", "\n") | 
| jbe/bsw@0 | 108               translations[key] = false | 
| jbe/bsw@0 | 109             end | 
| jbe/bsw@0 | 110           end | 
| jbe/bsw@0 | 111         end | 
| jbe/bsw@0 | 112       end | 
| jbe/bsw@0 | 113       traverse(path .. "/" .. filename) | 
| jbe/bsw@0 | 114     end | 
| jbe/bsw@0 | 115   end | 
| jbe/bsw@0 | 116   return true | 
| jbe/bsw@0 | 117 end | 
| jbe/bsw@0 | 118 for num, directory in ipairs(directories) do | 
| jbe/bsw@0 | 119   io.stderr:write('Parsing files in directory "', directory, '".\n') | 
| jbe/bsw@0 | 120   if not traverse(directory) then | 
| jbe/bsw@0 | 121     error('Could not read directory "' .. directory .. '".') | 
| jbe/bsw@0 | 122   end | 
| jbe/bsw@0 | 123 end | 
| jbe/bsw@0 | 124 | 
| jbe/bsw@0 | 125 local function update_translation(key, value) | 
| jbe/bsw@0 | 126   if #directories > 0 then | 
| jbe/bsw@0 | 127     if translations[key] ~= nil then translations[key] = value end | 
| jbe/bsw@0 | 128   else | 
| jbe/bsw@0 | 129     translations[key] = value | 
| jbe/bsw@0 | 130   end | 
| jbe/bsw@0 | 131 end | 
| jbe/bsw@0 | 132 | 
| jbe/bsw@0 | 133 if in_filetype == "po" then | 
| jbe/bsw@0 | 134   io.stderr:write('Reading translations from po file "', in_filename, '".\n') | 
| jbe/bsw@0 | 135   local next_line = io.lines(in_filename) | 
| jbe/bsw@0 | 136   for line in next_line do | 
| jbe/bsw@0 | 137     if not line then break end | 
| jbe/bsw@0 | 138     local key = string.match(line, '^msgid%s*"(.*)"%s*$') | 
| jbe/bsw@0 | 139     if key then | 
| jbe/bsw@0 | 140       local line = next_line() | 
| jbe/bsw@0 | 141       local value = string.match(line, '^msgstr%s*"(.*)"%s*$') | 
| jbe/bsw@0 | 142       if not value then | 
| jbe/bsw@0 | 143         error("Expected msgstr line in po file.") | 
| jbe/bsw@0 | 144       end | 
| jbe/bsw@0 | 145       if translations[key] then | 
| jbe/bsw@0 | 146         error("Duplicate key '" .. key .. "' in po file.") | 
| jbe/bsw@0 | 147       end | 
| jbe/bsw@0 | 148       if value == "" then value = false end | 
| jbe/bsw@0 | 149       update_translation(key, value) | 
| jbe/bsw@0 | 150     end | 
| jbe/bsw@0 | 151   end | 
| jbe/bsw@0 | 152 elseif in_filetype == "lua" then | 
| jbe/bsw@0 | 153   io.stderr:write('Reading translations from lua file "', in_filename, '".\n') | 
| jbe/bsw@0 | 154   local func = assert(loadfile(in_filename)) | 
| jbe/bsw@0 | 155   setfenv(func, {}) | 
| jbe/bsw@0 | 156   local updates = func() | 
| jbe/bsw@0 | 157   for key, value in pairs(updates) do | 
| jbe/bsw@0 | 158     update_translation(key, value) | 
| jbe/bsw@0 | 159   end | 
| jbe/bsw@0 | 160 end | 
| jbe/bsw@0 | 161 | 
| jbe/bsw@0 | 162 local translation_keys = {} | 
| jbe/bsw@0 | 163 for key in pairs(translations) do | 
| jbe/bsw@0 | 164   table.insert(translation_keys, key) | 
| jbe/bsw@0 | 165 end | 
| jbe/bsw@0 | 166 table.sort(translation_keys) | 
| jbe/bsw@0 | 167 | 
| jbe/bsw@0 | 168 if out_filetype == "po" then | 
| jbe/bsw@0 | 169   io.stderr:write('Writing translations to po file "', out_filename, '".\n') | 
| jbe/bsw@0 | 170   local file = assert(io.open(out_filename, "w")) | 
| jbe/bsw@0 | 171   for num, key in ipairs(translation_keys) do | 
| jbe/bsw@0 | 172     local value = translations[key] | 
| jbe/bsw@0 | 173     file:write('msgid "', key, '"\nmsgstr "', value or "", '"\n\n') | 
| jbe/bsw@0 | 174   end | 
| jbe/bsw@0 | 175   io.close(file) | 
| jbe/bsw@0 | 176 elseif out_filetype == "lua" then | 
| jbe/bsw@0 | 177   io.stderr:write('Writing translations to lua file "', out_filename, '".\n') | 
| jbe/bsw@0 | 178   local file = assert(io.open(out_filename, "w")) | 
| jbe/bsw@0 | 179   file:write("#!/usr/bin/env lua\n", "return {\n") | 
| jbe/bsw@0 | 180   for num, key in ipairs(translation_keys) do | 
| jbe/bsw@0 | 181     local value = translations[key] | 
| jbe/bsw@0 | 182     if value then | 
| jbe/bsw@4 | 183       file:write((string.format("[%q] = %q;\n", key, value):gsub("\\\n", "\\n"))) -- double () important to hide second result of gsub | 
| jbe/bsw@0 | 184     else | 
| jbe/bsw@4 | 185       file:write((string.format("[%q] = false;\n", key):gsub("\\\n", "\\n"))) -- double () important to hide second result of gsub | 
| jbe/bsw@0 | 186     end | 
| jbe/bsw@0 | 187   end | 
| jbe/bsw@0 | 188   file:write("}\n") | 
| jbe/bsw@0 | 189   io.close(file) | 
| jbe/bsw@0 | 190 end |