summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Martínez <kaeza@users.sf.net>2013-02-27 04:27:51 -0200
committerDiego Martínez <kaeza@users.sf.net>2013-02-27 04:27:51 -0200
commit11b9e0596c0567c5b6737394f9f6a8aaac560262 (patch)
tree67848523dbbc16fd14bb3ec2dac8399241f86f7b
first commit
-rw-r--r--README.txt96
-rw-r--r--init.lua0
-rw-r--r--intllib.lua91
-rw-r--r--locale/es.txt4
-rwxr-xr-xtools/findtext.lua257
5 files changed, 448 insertions, 0 deletions
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..6677ccd
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,96 @@
+
+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 (minetest.get_modpath("intllib")) then
+ dofile(minetest.get_modpath("intllib").."/intllib.lua")
+ S = intllib.Getter(minetest.get_current_modname())
+ else
+ S = function ( s ) return s end
+ end
+
+Note that by using this snippet, you don't need to depend on `intllib'. In
+fact, the mod's `init.lua' is a no-op; you need to explicitly execute intllib's
+`intllib.lua' file.
+Also note that if the intllib "mod" 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.
+ Blank lines not containing an equals sign are also ignored.
+ Hello, World! = Hola, Mundo!
+ String with\nnewlines and \ttabs = Cadena con\nsaltos de linea y\ttabuladores
+ 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 = <language code>
+
+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.
+
+Frequently Asked Questions
+--------------------------
+Q: Were you bored when you did this?
+A: Yes.
+
+Q: Why are my texts are not translated?
+A: RTFM...or ask in the topic 8-----)
+
+Q: How come the README is bigger than the actual code?
+A: Because I'm adding silly unfunny questions here...and because there are
+ some users that are too lazy to understand how the code works, so I have
+ to document things.
+
+Q: I don't like this sh*t!
+A: That's not a question.
+
+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/init.lua b/init.lua
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/init.lua
diff --git a/intllib.lua b/intllib.lua
new file mode 100644
index 0000000..a59d5b4
--- /dev/null
+++ b/intllib.lua
@@ -0,0 +1,91 @@
+
+intllib = { };
+
+local strings = { };
+
+local INTLLIB_DEBUG = true;
+
+local LANG = minetest.setting_get("language") or os.getenv("LANG") or "en";
+LANG = LANG:sub(1, 2);
+
+local TRACE;
+
+if (INTLLIB_DEBUG) then
+ TRACE = function ( s )
+ print("*** DEBUG: "..s);
+ end
+else
+ TRACE = function ( ) end
+end
+
+local repr2esc = {
+ ["n"] = "\n";
+ ["r"] = "";
+ ["t"] = "\t";
+ ["\\"] = "\\";
+ ["\""] = "\"";
+};
+
+local esc2repr = {
+ ["\n"] = "\\n";
+ ["\r"] = "";
+ ["\t"] = "\\t";
+ ["\\"] = "\\\\";
+ ["\\\""] = "\"";
+};
+
+local function parse ( s )
+ return s:gsub("\\([nrt\"\'\\\\])", function ( c )
+ return (repr2esc[c] or c);
+ end);
+end
+
+local function repr ( s )
+ return s:gsub("[\n\t\"\'\\\\]", function ( c )
+ return (esc2repr[c] or c);
+ end);
+end
+
+local function do_load_strings ( f )
+ local msgstr = { };
+ for line in f:lines() do
+ line = line:trim();
+ if ((line ~= "") and (line:sub(1, 1) ~= "#")) then
+ local pos = line:find("=", 1, true);
+ while (pos and (line:sub(pos - 1, pos - 1) == "\\")) do
+ local pos = line:find("=", pos + 1, true);
+ end
+ if (pos) then
+ local msgid = line:sub(1, pos - 1):trim();
+ local str = line:sub(pos + 1):trim();
+ msgstr[msgid] = parse(str);
+ end
+ end
+ end
+ return msgstr;
+end
+
+function intllib.load_strings ( modname )
+ local f, e = io.open(minetest.get_modpath(modname).."/locale/"..LANG..".txt");
+ if (f) then
+ local strings;
+ strings = do_load_strings(f);
+ f:close();
+ return strings;
+ else
+ return nil, "Could not load '"..LANG.."' texts: "..e;
+ end
+end
+
+local getters = { };
+
+function intllib.Getter ( modname )
+ if (not modname) then modname = minetest.get_current_modname(); end
+ if (not getters[modname]) then
+ local msgstr = intllib.load_strings(modname) or { };
+ getters[modname] = function ( s )
+ return msgstr[repr(s)] or s;
+ end;
+ end
+ return getters[modname];
+end
diff --git a/locale/es.txt b/locale/es.txt
new file mode 100644
index 0000000..5f8dc22
--- /dev/null
+++ b/locale/es.txt
@@ -0,0 +1,4 @@
+
+Hello %s!::¡Hola %s!
+Blah blah...\nBlah blah...::Bla bla...\nBla bla...
+Bye!::¡Adios!
diff --git a/tools/findtext.lua b/tools/findtext.lua
new file mode 100755
index 0000000..54bb229
--- /dev/null
+++ b/tools/findtext.lua
@@ -0,0 +1,257 @@
+#! /usr/bin/env lua
+
+local function listdir ( dir )
+ local LS;
+ if (os.getenv("WINDIR")) then
+ LS = 'dir /b %s';
+ else
+ LS = 'ls %s';
+ end
+ local tmp = os.tmpname();
+ local r = os.execute(LS:format(dir).." > "..tmp)
+ if ((r ~= nil) and (r == 0)) then
+ local list = { };
+ local f = io.open(tmp);
+ if (not f) then
+ os.remove(tmp);
+ return nil;
+ end
+ for line in f:lines() do
+ list[#list + 1] = line;
+ end
+ f:close();
+ os.remove(tmp);
+ return list;
+ end
+ os.remove(tmp);
+ return nil;
+end
+
+local function StringOutput ( s, file )
+ return {
+ _buf = (s or "");
+ _file = file;
+ write = function ( self, ... )
+ local spc = false;
+ for _, v in ipairs({...}) do
+ if (spc) then self._buf = self._buf.." "; end
+ spc = true;
+ self._buf = self._buf..tostring(v);
+ end
+ end;
+ print = function ( self, ... )
+ local spc = false;
+ for _, v in ipairs({...}) do
+ if (spc) then self._buf = self._buf.." "; end
+ spc = true;
+ self._buf = self._buf..tostring(v);
+ end
+ self._buf = self._buf.."\n";
+ end;
+ tostring = function ( self )
+ return self._buf;
+ end;
+ flush = function ( self, file )
+ local f = (file or self._file or io.stdout);
+ f:write(self._buf);
+ f:flush();
+ end;
+ };
+end
+
+local function err_print ( s )
+ io.stderr:write((s or "").."\n");
+end
+
+local function ArgParser ( appname, usageline, description )
+ return {
+ prefix = ((appname and (appname..": ")) or "");
+ description = description;
+ options = {
+ { name = "help";
+ short = "h";
+ long = "help";
+ help = "Show this help screen and exit.";
+ },
+ };
+ values = { };
+ inputs = { };
+ no_exit = (appname == nil);
+ add_option = function ( self, name, short, long, has_arg, arg_name, help )
+ self.options[#self.options+1] = {
+ name = name;
+ short = short;
+ long = long;
+ has_arg = has_arg;
+ arg_name = arg_name;
+ help = help;
+ };
+ end;
+ parse = function ( self, args, no_exit )
+ local opts = { };
+ local inputs = { };
+ local i = 1;
+ while (i <= #args) do
+ local a = args[i];
+ if (a:sub(1, 1) == '-') then
+ local found = false;
+ if ((a == "-h") or (a == "--help")) then
+ if (no_exit or self.no_exit) then
+ return nil, "--help";
+ else
+ self:show_usage();
+ os.exit(0);
+ end
+ end
+ for _, opt in ipairs(self.options) do
+ if ((a == "-"..opt.short) or (a == "--"..opt.long)) then
+ if (opt.has_arg) then
+ i = i + 1;
+ if (not args[i]) then
+ local msg = self.prefix.."option `"..a.."' requires an argument.";
+ if (no_exit) then
+ return nil, msg;
+ else
+ err_print(msg);
+ os.exit(-1);
+ end
+ end
+ opts[opt.name] = args[i];
+ else
+ opts[opt.name] = true;
+ end
+ found = true;
+ break;
+ end
+ end
+ if (not found) then
+ local msg = self.prefix.."unrecognized option `"..a.."'. Try `--help'.";
+ if (no_exit or self.no_exit) then
+ return nil, msg;
+ else
+ err_print(msg);
+ os.exit(-1);
+ end
+ end
+ else
+ inputs[#inputs + 1] = a;
+ end
+ i = i + 1;
+ end
+ return opts, inputs;
+ end;
+ show_usage = function ( self, extramsg )
+ if (extramsg) then
+ err_print(self.prefix..extramsg);
+ end
+ err_print("Usage: "..appname.." "..(usageline or ""));
+ if (self.description) then
+ err_print(self.description)
+ end
+ if (#self.options > 0) then
+ err_print("\nAvailable Options:");
+ local optwidth = 0;
+ local optline = { };
+ for _, opt in ipairs(self.options) do
+ local sh = (opt.short and "-"..opt.short);
+ local ln = (opt.long and "--"..opt.long);
+ local sep = (sh and ln and ",") or " ";
+ sh = sh or "";
+ ln = ln or "";
+ if (opt.long and opt.has_arg) then
+ ln = ln.." "..opt.arg_name;
+ end
+ optline[#optline + 1] = sh..sep..ln;
+ if (optline[#optline]:len() > optwidth) then
+ optwidth = optline[#optline]:len();
+ end
+ end
+ for i, opt in ipairs(self.options) do
+ local sep = string.rep(" ", optwidth - optline[i]:len()).." ";
+ err_print(" "..optline[i]..sep..opt.help);
+ end
+ err_print();
+ if (self.footer) then
+ err_print(self.footer.."\n");
+ end
+ end
+ end;
+ };
+end
+
+local function main ( arg )
+
+ local APPNAME = "findtext.lua";
+
+ local ap = ArgParser(APPNAME, "[OPTIONS] FILES...");
+ ap:add_option("output", "o", "output", true, "FILE", "Write translated strings to FILE. (default: stdout)");
+ ap:add_option("langname", "l", "langname", true, "NAME", "Set the language name to NAME.");
+ ap:add_option("author", "a", "author", true, "NAME", "Set the author to NAME.");
+ ap:add_option("mail", "m", "mail", true, "EMAIL", "Set the author contact address to EMAIL.");
+ ap.description = "Finds all the strings that need to be localized, by"..
+ " searching for things\n like `S(\"foobar\")'.";
+ ap.footer = "Report bugs to <lkaezadl3@gmail.com>.";
+
+ local opts, files = ap:parse(arg);
+
+ if (#files == 0) then
+ files = listdir("*.lua");
+ if ((not files) or (#files == 0)) then
+ io.stderr:write(APPNAME..": no input files\n");
+ os.exit(-1);
+ end
+ end
+
+ local buffer = StringOutput();
+
+ buffer:write("\n");
+
+ if (opts.langname) then
+ buffer:write("# Language: "..opts.langname.."\n");
+ end
+ if (opts.author) then
+ buffer:write("# Author: "..opts.author);
+ if (opts.mail) then
+ buffer:write(" <"..opts.mail..">");
+ end
+ buffer:write("\n");
+ end
+ if (opts.author or opts.langname) then
+ buffer:write("\n");
+ end
+
+ for _, file in ipairs(files) do
+ local infile, e = io.open(file);
+ if (not infile) then
+ io.stderr:write(APPNAME..": "..e.."\n");
+ os.exit(1);
+ end
+ for line in infile:lines() do
+ local s = line:match('.-S%("([^"]*)"%).*');
+ if (s) then
+ buffer:write(s.." = \n");
+ end
+ end
+ infile:close();
+ end
+
+ local outfile, e;
+ if (opts.output) then
+ outfile, e = io.open(opts.output, "w");
+ if (not outfile) then
+ io.stderr:write(APPNAME..": "..e.."\n");
+ os.exit(1);
+ end
+ else
+ outfile = io.stdout;
+ end
+
+ buffer:flush(outfile);
+
+ if (outfile ~= io.stdout) then
+ outfile:close();
+ end
+
+end
+
+main(arg);