| 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@64
 | 
     5     extos = 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@65
 | 
    67 if #directories > 0 and not extos.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@65
 | 
    84   local filenames = extos.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@118
 | 
    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@118
 | 
   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@64
 | 
   154   local func
 | 
| 
jbe@64
 | 
   155   if _ENV then
 | 
| 
jbe@64
 | 
   156     func = assert(loadfile(in_filename, "t", {}))
 | 
| 
jbe@64
 | 
   157   else
 | 
| 
jbe@64
 | 
   158     func = assert(loadfile(in_filename))
 | 
| 
jbe@64
 | 
   159     setfenv(func, {})
 | 
| 
jbe@64
 | 
   160   end
 | 
| 
jbe/bsw@0
 | 
   161   local updates = func()
 | 
| 
jbe/bsw@0
 | 
   162   for key, value in pairs(updates) do
 | 
| 
jbe/bsw@0
 | 
   163     update_translation(key, value)
 | 
| 
jbe/bsw@0
 | 
   164   end
 | 
| 
jbe/bsw@0
 | 
   165 end
 | 
| 
jbe/bsw@0
 | 
   166 
 | 
| 
jbe/bsw@0
 | 
   167 local translation_keys = {}
 | 
| 
jbe/bsw@0
 | 
   168 for key in pairs(translations) do
 | 
| 
jbe/bsw@0
 | 
   169   table.insert(translation_keys, key)
 | 
| 
jbe/bsw@0
 | 
   170 end
 | 
| 
jbe/bsw@0
 | 
   171 table.sort(translation_keys)
 | 
| 
jbe/bsw@0
 | 
   172 
 | 
| 
jbe/bsw@0
 | 
   173 if out_filetype == "po" then
 | 
| 
jbe/bsw@0
 | 
   174   io.stderr:write('Writing translations to po file "', out_filename, '".\n')
 | 
| 
jbe/bsw@0
 | 
   175   local file = assert(io.open(out_filename, "w"))
 | 
| 
jbe/bsw@0
 | 
   176   for num, key in ipairs(translation_keys) do
 | 
| 
jbe/bsw@0
 | 
   177     local value = translations[key]
 | 
| 
jbe/bsw@0
 | 
   178     file:write('msgid "', key, '"\nmsgstr "', value or "", '"\n\n')
 | 
| 
jbe/bsw@0
 | 
   179   end
 | 
| 
jbe/bsw@0
 | 
   180   io.close(file)
 | 
| 
jbe/bsw@0
 | 
   181 elseif out_filetype == "lua" then
 | 
| 
jbe/bsw@0
 | 
   182   io.stderr:write('Writing translations to lua file "', out_filename, '".\n')
 | 
| 
jbe/bsw@0
 | 
   183   local file = assert(io.open(out_filename, "w"))
 | 
| 
jbe/bsw@0
 | 
   184   file:write("#!/usr/bin/env lua\n", "return {\n")
 | 
| 
jbe/bsw@0
 | 
   185   for num, key in ipairs(translation_keys) do
 | 
| 
jbe/bsw@0
 | 
   186     local value = translations[key]
 | 
| 
jbe/bsw@0
 | 
   187     if value then
 | 
| 
jbe/bsw@4
 | 
   188       file:write((string.format("[%q] = %q;\n", key, value):gsub("\\\n", "\\n"))) -- double () important to hide second result of gsub
 | 
| 
jbe/bsw@0
 | 
   189     else
 | 
| 
jbe/bsw@4
 | 
   190       file:write((string.format("[%q] = false;\n", key):gsub("\\\n", "\\n"))) -- double () important to hide second result of gsub
 | 
| 
jbe/bsw@0
 | 
   191     end
 | 
| 
jbe/bsw@0
 | 
   192   end
 | 
| 
jbe/bsw@0
 | 
   193   file:write("}\n")
 | 
| 
jbe/bsw@0
 | 
   194   io.close(file)
 | 
| 
jbe/bsw@0
 | 
   195 end
 |