commit 11b9e0596c0567c5b6737394f9f6a8aaac560262 Author: Diego Martínez Date: Wed Feb 27 04:27:51 2013 -0200 first commit 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 = + +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 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 ."; + + 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);