From b69387ea94a253ef93e27a266785b3fc7ef02392 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Wed, 1 Oct 2014 14:16:35 +0200 Subject: [PATCH] Import intllib 0.1.0 --- mods/intllib/README.txt | 76 ++++++++++++++++ mods/intllib/init.lua | 58 ++++++++++++ mods/intllib/intllib.lua | 3 + mods/intllib/lib.lua | 45 ++++++++++ mods/intllib/tools/findtext.lua | 142 ++++++++++++++++++++++++++++++ mods/intllib/tools/updatetext.lua | 141 +++++++++++++++++++++++++++++ 6 files changed, 465 insertions(+) create mode 100644 mods/intllib/README.txt create mode 100644 mods/intllib/init.lua create mode 100644 mods/intllib/intllib.lua create mode 100644 mods/intllib/lib.lua create mode 100755 mods/intllib/tools/findtext.lua create mode 100644 mods/intllib/tools/updatetext.lua diff --git a/mods/intllib/README.txt b/mods/intllib/README.txt new file mode 100644 index 0000000..37f5c48 --- /dev/null +++ b/mods/intllib/README.txt @@ -0,0 +1,76 @@ + +Internationalization Lib for Minetest +By Diego Martínez (a.k.a. "Kaeza"). +Released as WTFPL. + +This mod is an attempt at providing internationalization support for mods +(something Minetest currently lacks). + +How do I use it? +In order to enable it for your mod, copy the following code snippet and paste +it at the beginning of your source file(s): + + -- Boilerplate to support localized strings if intllib mod is installed. + local S + if intllib then + S = intllib.Getter() + else + S = function(s) return s end + end + +You will also need to optionally depend on intllib, to do so add "intllib?" to +a empty line in your depends.txt. Also note that if intllib is not installed, +the S() function is defined so it returns the string unchanged. This is done +so you don't have to sprinkle tons of 'if's (or similar constructs) to check +if the lib is actually installed. + +Next, for each "translatable" string in your sources, use the S() function +(defined in the snippet) to return the translated string. For example: + + minetest.register_node("mymod:mynode", { + description = S("My Fabulous Node"), + <...> + }) + +Then, you create a `locale' directory inside your mod directory, with files +named after the two-letter ISO Language Code of the languages you want to +support. Here's an example for a Spanish locale file (`es.txt'): + + # Lines beginning with a pound sign are comments and are effectively ignored + # by the reader. Note that comments only span until the end of the line; + # there's no support for multiline comments. + Hello, World! = Hola, Mundo! + String with\nnewlines = Cadena con\nsaltos de linea + String with an \= equals sign = Cadena con un signo de \= igualdad + +Since there's currently no portable way to detect the language, this library +tries several alternatives, and uses the first one found: + - `language' setting in `minetest.conf' + - `LANG' environment variable (this is always set on Unix-like OSes). + - Default of "en". +Note that in any case only up to the first two characters are used, so for +example, the settings "de_DE.UTF-8", "de_DE", and "de" are all equal. +Windows users have no `LANG' environment variable by default. To add it, do +the following: + - Click Start->Settings->Control Panel. + - Start the "System" applet. + - Click on the "Advanced" tab. + - Click on the "Environment variables" button + - Click "New". + - Type "LANG" (without quotes) as name and the language code as value. + - Click OK until all dialogs are closed. +Alternatively for all platforms, if you don't want to modify system settings, +you may add the following line to your `minetest.conf' file: + language = + +Also note that there are some problems with using accented, and in general +non-latin characters in strings. Until a fix is found, please limit yourself +to using only US-ASCII characters. + +Thanks for reading up to this point. +Should you have any comments/suggestions, please post them in the forum topic. + +Let there be translated texts! :P +-- +Yours Truly, +Kaeza diff --git a/mods/intllib/init.lua b/mods/intllib/init.lua new file mode 100644 index 0000000..62a8f51 --- /dev/null +++ b/mods/intllib/init.lua @@ -0,0 +1,58 @@ + +-- Support the old multi-load method +intllib = intllib or {} + +local MP = minetest.get_modpath("intllib") + +dofile(MP.."/lib.lua") + +local strings = {} + +local LANG = minetest.setting_get("language") +if not (LANG and (LANG ~= "")) then LANG = os.getenv("LANG") end +if not (LANG and (LANG ~= "")) then LANG = "en" end +LANG = LANG:sub(1, 2) + +-- Support the old multi-load method +intllib.getters = intllib.getters or {} + +intllib.strings = {} + +local function noop_getter(s) + return s +end + +function intllib.Getter(modname) + modname = modname or minetest.get_current_modname() + if not intllib.getters[modname] then + local modpath = minetest.get_modpath(modname) + if modpath then + local filename = modpath.."/locale/"..LANG..".txt" + local msgstr = intllib.load_strings(filename) + intllib.strings[modname] = msgstr or false + if msgstr then + intllib.getters[modname] = function (s) + if msgstr[s] and msgstr[s] ~= "" then + return msgstr[s] + end + return s + end + else + intllib.getters[modname] = noop_getter + end + end + end + return intllib.getters[modname] +end + +function intllib.get_strings(modname) + modname = modname or minetest.get_current_modname() + local msgstr = intllib.strings[modname] + if msgstr == nil then + local modpath = minetest.get_modpath(modname) + msgstr = intllib.load_strings(modpath.."/locale/"..LANG..".txt") + intllib.strings[modname] = msgstr + end + return msgstr or nil +end + diff --git a/mods/intllib/intllib.lua b/mods/intllib/intllib.lua new file mode 100644 index 0000000..adb0f88 --- /dev/null +++ b/mods/intllib/intllib.lua @@ -0,0 +1,3 @@ +-- Support for the old multi-load method +dofile(minetest.get_modpath("intllib").."/init.lua") + diff --git a/mods/intllib/lib.lua b/mods/intllib/lib.lua new file mode 100644 index 0000000..b3f183b --- /dev/null +++ b/mods/intllib/lib.lua @@ -0,0 +1,45 @@ + +intllib = intllib or {} + +local escapes = { + ["\\"] = "\\", + ["n"] = "\n", +} + +local function unescape(s) + return s:gsub("([\\]?)\\(.)", function(slash, what) + if slash and (slash ~= "") then + return "\\"..what + else + return escapes[what] or what + end + end) +end + +local function find_eq(s) + for slashes, pos in s:gmatch("([\\]*)=()") do + if (slashes:len() % 2) == 0 then + return pos - 1 + end + end +end + +function intllib.load_strings(filename) + local file, err = io.open(filename, "r") + if not file then + return nil + end + local strings = {} + for line in file:lines() do + line = line:trim() + if line ~= "" and line:sub(1, 1) ~= "#" then + local pos = find_eq(line) + if pos then + local msgid = unescape(line:sub(1, pos - 1):trim()) + strings[msgid] = unescape(line:sub(pos + 1):trim()) + end + end + end + file:close() + return strings +end diff --git a/mods/intllib/tools/findtext.lua b/mods/intllib/tools/findtext.lua new file mode 100755 index 0000000..63137e0 --- /dev/null +++ b/mods/intllib/tools/findtext.lua @@ -0,0 +1,142 @@ +#! /usr/bin/env lua + +local me = arg[0]:gsub(".*[/\\](.*)$", "%1") + +local function err(fmt, ...) + io.stderr:write(("%s: %s\n"):format(me, fmt:format(...))) + os.exit(1) +end + +local output +local inputs = { } +local lang +local author + +local i = 1 + +local function usage() + print([[ +Usage: ]]..me..[[ [OPTIONS] FILE... + +Extract translatable strings from the given FILE(s). + +Available options: + -h,--help Show this help screen and exit. + -o,--output X Set output file (default: stdout). + -a,--author X Set author. + -l,--lang X Set language name. +]]) + os.exit(0) +end + +while i <= #arg do + local a = arg[i] + if (a == "-h") or (a == "--help") then + usage() + elseif (a == "-o") or (a == "--output") then + i = i + 1 + if i > #arg then + err("missing required argument to `%s'", a) + end + output = arg[i] + elseif (a == "-a") or (a == "--author") then + i = i + 1 + if i > #arg then + err("missing required argument to `%s'", a) + end + author = arg[i] + elseif (a == "-l") or (a == "--lang") then + i = i + 1 + if i > #arg then + err("missing required argument to `%s'", a) + end + lang = arg[i] + elseif a:sub(1, 1) ~= "-" then + table.insert(inputs, a) + else + err("unrecognized option `%s'", a) + end + i = i + 1 +end + +if #inputs == 0 then + err("no input files") +end + +local outfile = io.stdout + +local function printf(fmt, ...) + outfile:write(fmt:format(...)) +end + +if output then + local e + outfile, e = io.open(output, "w") + if not outfile then + err("error opening file for writing: %s", e) + end +end + +if author or lang then + outfile:write("\n") +end + +if lang then + printf("# Language: %s\n", lang) +end + +if author then + printf("# Author: %s\n", author) +end + +if author or lang then + outfile:write("\n") +end + +local escapes = { + ["\n"] = "\\n", + ["="] = "\\=", + ["\\"] = "\\\\", +} + +local function escape(s) + return s:gsub("[\\\n=]", escapes) +end + +local messages = { } + +for _, file in ipairs(inputs) do + local infile, e = io.open(file, "r") + if infile then + for line in infile:lines() do + for s in line:gmatch('S%("([^"]*)"%)') do + table.insert(messages, s) + end + end + infile:close() + else + io.stderr:write(("%s: WARNING: error opening file: %s\n"):format(me, e)) + end +end + +table.sort(messages) + +local last_msg + +for i, msg in ipairs(messages) do + if msg ~= last_msg then + printf("%s =\n", escape(msg)) + end + last_msg = msg +end + +if output then + outfile:close() +end + +--[[ +TESTS: +S("foo") S("bar") +S("bar") +S("foo") +]] diff --git a/mods/intllib/tools/updatetext.lua b/mods/intllib/tools/updatetext.lua new file mode 100644 index 0000000..00f9bf6 --- /dev/null +++ b/mods/intllib/tools/updatetext.lua @@ -0,0 +1,141 @@ +#! /usr/bin/env lua + +local basedir = "" +if arg[0]:find("[/\\]") then + basedir = arg[0]:gsub("(.*[/\\]).*$", "%1"):gsub("\\", "/") +end +if basedir == "" then basedir = "./" end + +-- Required by load_strings() +function string.trim(s) + return s:gsub("^%s*(.-)%s*$", "%1") +end + +dofile(basedir.."/../lib.lua") + +local me = arg[0]:gsub(".*[/\\](.*)$", "%1") + +local function err(fmt, ...) + io.stderr:write(("%s: %s\n"):format(me, fmt:format(...))) + os.exit(1) +end + +local template +local catalogs = { } + +local function usage() + print([[ +Usage: ]]..me..[[ [OPTIONS] TEMPLATE CATALOG... + +Update a catalog with new strings from a template. + +Available options: + -h,--help Show this help screen and exit. + -o,--output X Set output file (default: stdout). + +Messages in the template that are not on the catalog are added to the +catalog at the end. + +This tool also checks messages that are in the catalog but not in the +template, and reports such lines. It's up to the user to remove such +lines, if so desired. +]]) + os.exit(0) +end + +local i = 1 + +while i <= #arg do + local a = arg[i] + if (a == "-h") or (a == "--help") then + usage() + elseif (a == "-o") or (a == "--output") then + i = i + 1 + if i > #arg then + err("missing required argument to `%s'", a) + end + elseif (a == "-c") or (a == "--comment") then + old_msg_mode = "c" + elseif (a == "-d") or (a == "--delete") then + old_msg_mode = "d" + elseif a:sub(1, 1) ~= "-" then + if not template then + template = a + else + table.insert(catalogs, a) + end + else + err("unrecognized option `%s'", a) + end + i = i + 1 +end + +if not template then + err("no template specified") +elseif #catalogs == 0 then + err("no catalogs specified") +end + +local f, e = io.open(template, "r") +if not f then + err("error opening template: %s", e) +end + +local function printf(fmt, ...) + outfile:write(fmt:format(...)) +end + +local escapes = { ["\n"] = "\\n", ["="] = "\\=", ["\\"] = "\\\\", } +local function escape(s) + return s:gsub("[\\\n=]", escapes) +end + +if output then + local e + outfile, e = io.open(output, "w") + if not outfile then + err("error opening file for writing: %s", e) + end +end + +local function printf(fmt, ...) + io.stdout:write(fmt:format(...)) +end + +local template_msgs = intllib.load_strings(template) + +for _, file in ipairs(catalogs) do + print("Processing: "..file) + local catalog_msgs = intllib.load_strings(file) + local dirty_lines = { } + if catalog_msgs then + -- Add new entries from template. + for k in pairs(template_msgs) do + if not catalog_msgs[k] then + print("NEW: "..k) + table.insert(dirty_lines, escape(k).." =") + end + end + -- Check for old messages. + for k, v in pairs(catalog_msgs) do + if not template_msgs[k] then + print("OLD: "..k) + end + end + if #dirty_lines > 0 then + local outf, e = io.open(file, "a+") + if outf then + outf:write("\n") + for _, line in ipairs(dirty_lines) do + outf:write(line) + outf:write("\n") + end + outf:close() + else + io.stderr:write(("%s: WARNING: cannot write: %s\n"):format(me, e)) + end + end + else + io.stderr:write(("%s: WARNING: could not load catalog\n"):format(me)) + end +end