jbe/bsw@0: #!/usr/bin/env lua jbe/bsw@0: jbe/bsw@0: if not pcall( jbe/bsw@0: function() jbe/bsw@0: require "extos" jbe/bsw@0: end jbe/bsw@0: ) then jbe/bsw@0: io.stderr:write('Could not load library "extos".\n') jbe/bsw@0: io.stderr:write('Hint: Set LUA_CPATH="/path_to_extos_library/?.so;;"\n') jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: local args = {...} jbe/bsw@0: jbe/bsw@0: if #args == 0 then jbe/bsw@0: print() jbe/bsw@0: print("This program creates translation files by traversing source directories.") jbe/bsw@0: print() jbe/bsw@0: print("Two formats are supported: lua files and po files.") jbe/bsw@0: print("At runtime a lua file is needed.") jbe/bsw@0: print("For use with po-file editors you may want to create po files first though.") jbe/bsw@0: print() jbe/bsw@0: print("Create or update a lua file: langtool.lua dir1/ dir2/ ... .lua") jbe/bsw@0: print("Create or update a po file: langtool.lua dir1/ dir2/ ... .po") jbe/bsw@0: print("Convert po file to lua file: langtool.lua .po .lua") jbe/bsw@0: print("Convert lua file to po file: langtool.lua .lua .po") jbe/bsw@0: print() jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local in_filename, in_filetype, out_filename, out_filetype jbe/bsw@0: local directories = {} jbe/bsw@0: jbe/bsw@0: for arg_num, arg in ipairs(args) do jbe/bsw@0: local function arg_error(msg) jbe/bsw@0: error("Illegal command line argument #" .. arg_num .. ": " .. msg) jbe/bsw@0: end jbe/bsw@0: local po = string.match(arg, "^po:(.*)$") or string.match(arg, "^(.*%.po)$") jbe/bsw@0: local lua = string.match(arg, "^lua:(.*)$") or string.match(arg, "^(.*%.lua)$") jbe/bsw@0: local filetype jbe/bsw@0: if po and not lua then filetype = "po" jbe/bsw@0: filetype = "po" jbe/bsw@0: elseif lua and not po then filetype = "lua" jbe/bsw@0: filetype = "lua" jbe/bsw@0: else jbe/bsw@0: filetype = "path" jbe/bsw@0: end jbe/bsw@0: if filetype == "path" then jbe/bsw@0: table.insert(directories, arg) jbe/bsw@0: elseif filetype == "po" or filetype == "lua" then jbe/bsw@0: if not out_filename then jbe/bsw@0: out_filename = arg jbe/bsw@0: out_filetype = filetype jbe/bsw@0: elseif not in_filename then jbe/bsw@0: in_filename = out_filename jbe/bsw@0: in_filetype = out_filetype jbe/bsw@0: out_filename = arg jbe/bsw@0: out_filetype = filetype jbe/bsw@0: else jbe/bsw@0: arg_error("Only two language files (one input and one output file) can be specified.") jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: -- should not happen, as default type is "path" jbe/bsw@0: arg_error("Type not recognized") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if #directories > 0 and not os.listdir then jbe/bsw@0: io.stderr:write('Fatal: Cannot traverse directories without "extos" library -> Abort\n') jbe/bsw@0: os.exit(1) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if out_filename and not in_filename then jbe/bsw@0: local file = io.open(out_filename, "r") jbe/bsw@0: if file then jbe/bsw@0: in_filename = out_filename jbe/bsw@0: in_filetype = out_filetype jbe/bsw@0: file:close() jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local translations = { } jbe/bsw@0: jbe/bsw@0: local function traverse(path) jbe/bsw@0: local filenames = os.listdir(path) jbe/bsw@0: if not filenames then return false end jbe/bsw@0: for num, filename in ipairs(filenames) do jbe/bsw@0: if not string.find(filename, "^%.") then jbe/bsw@0: if string.find(filename, "%.lua$") then jbe/bsw@0: for line in io.lines(path .. "/" .. filename) do jbe/bsw@0: -- TODO: exact parsing of comments and escape characters jbe/bsw@0: for key in string.gmatch(line, "_%(?'([^'\]+)'") do jbe/bsw@0: if jbe/bsw@0: key ~= "([^" and jbe/bsw@0: (not string.find(key, "^%s*%.%.[^%.]")) and jbe/bsw@0: (not string.find(key, "^%s*,[^,]")) jbe/bsw@0: then jbe/bsw@4: local key = key:gsub("\\n", "\n") jbe/bsw@0: translations[key] = false jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: for key in string.gmatch(line, '_%(?"([^"\]+)"') do jbe/bsw@0: if jbe/bsw@0: key ~= "([^" and jbe/bsw@0: (not string.find(key, "^%s*%.%.[^%.]")) and jbe/bsw@0: (not string.find(key, "^%s*,[^,]")) jbe/bsw@0: then jbe/bsw@4: local key = key:gsub("\\n", "\n") jbe/bsw@0: translations[key] = false jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: traverse(path .. "/" .. filename) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: return true jbe/bsw@0: end jbe/bsw@0: for num, directory in ipairs(directories) do jbe/bsw@0: io.stderr:write('Parsing files in directory "', directory, '".\n') jbe/bsw@0: if not traverse(directory) then jbe/bsw@0: error('Could not read directory "' .. directory .. '".') jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local function update_translation(key, value) jbe/bsw@0: if #directories > 0 then jbe/bsw@0: if translations[key] ~= nil then translations[key] = value end jbe/bsw@0: else jbe/bsw@0: translations[key] = value jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: if in_filetype == "po" then jbe/bsw@0: io.stderr:write('Reading translations from po file "', in_filename, '".\n') jbe/bsw@0: local next_line = io.lines(in_filename) jbe/bsw@0: for line in next_line do jbe/bsw@0: if not line then break end jbe/bsw@0: local key = string.match(line, '^msgid%s*"(.*)"%s*$') jbe/bsw@0: if key then jbe/bsw@0: local line = next_line() jbe/bsw@0: local value = string.match(line, '^msgstr%s*"(.*)"%s*$') jbe/bsw@0: if not value then jbe/bsw@0: error("Expected msgstr line in po file.") jbe/bsw@0: end jbe/bsw@0: if translations[key] then jbe/bsw@0: error("Duplicate key '" .. key .. "' in po file.") jbe/bsw@0: end jbe/bsw@0: if value == "" then value = false end jbe/bsw@0: update_translation(key, value) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: elseif in_filetype == "lua" then jbe/bsw@0: io.stderr:write('Reading translations from lua file "', in_filename, '".\n') jbe/bsw@0: local func = assert(loadfile(in_filename)) jbe/bsw@0: setfenv(func, {}) jbe/bsw@0: local updates = func() jbe/bsw@0: for key, value in pairs(updates) do jbe/bsw@0: update_translation(key, value) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local translation_keys = {} jbe/bsw@0: for key in pairs(translations) do jbe/bsw@0: table.insert(translation_keys, key) jbe/bsw@0: end jbe/bsw@0: table.sort(translation_keys) jbe/bsw@0: jbe/bsw@0: if out_filetype == "po" then jbe/bsw@0: io.stderr:write('Writing translations to po file "', out_filename, '".\n') jbe/bsw@0: local file = assert(io.open(out_filename, "w")) jbe/bsw@0: for num, key in ipairs(translation_keys) do jbe/bsw@0: local value = translations[key] jbe/bsw@0: file:write('msgid "', key, '"\nmsgstr "', value or "", '"\n\n') jbe/bsw@0: end jbe/bsw@0: io.close(file) jbe/bsw@0: elseif out_filetype == "lua" then jbe/bsw@0: io.stderr:write('Writing translations to lua file "', out_filename, '".\n') jbe/bsw@0: local file = assert(io.open(out_filename, "w")) jbe/bsw@0: file:write("#!/usr/bin/env lua\n", "return {\n") jbe/bsw@0: for num, key in ipairs(translation_keys) do jbe/bsw@0: local value = translations[key] jbe/bsw@0: if value then jbe/bsw@4: file:write((string.format("[%q] = %q;\n", key, value):gsub("\\\n", "\\n"))) -- double () important to hide second result of gsub jbe/bsw@0: else jbe/bsw@4: file:write((string.format("[%q] = false;\n", key):gsub("\\\n", "\\n"))) -- double () important to hide second result of gsub jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: file:write("}\n") jbe/bsw@0: io.close(file) jbe/bsw@0: end