diff options
| author | Diego Martínez <kaeza@users.noreply.github.com> | 2017-01-21 01:04:03 -0300 | 
|---|---|---|
| committer | Diego Martínez <kaeza@users.noreply.github.com> | 2017-01-24 00:24:57 -0300 | 
| commit | b2551f6a2209b8a11b42834cb0d63f5c03a2b95f (patch) | |
| tree | 93e1ac391a3146431b273c94805f543b63b64fb1 | |
| parent | 4e067ec21906e9a27ec704dd5f34297b2592d6de (diff) | |
Add support for gettext message catalogs.
| -rw-r--r-- | LICENSE.md | 25 | ||||
| -rw-r--r-- | README-es.md | 41 | ||||
| -rw-r--r-- | README-es_UY.md | 136 | ||||
| -rw-r--r-- | README.md | 150 | ||||
| -rw-r--r-- | doc/developer.md | 96 | ||||
| -rw-r--r-- | doc/translator.md | 20 | ||||
| -rw-r--r-- | gettext.lua | 288 | ||||
| -rw-r--r-- | init.lua | 90 | ||||
| -rw-r--r-- | lib/intllib.lua | 45 | ||||
| -rw-r--r-- | tools/xgettext.bat | 33 | ||||
| -rwxr-xr-x | tools/xgettext.sh | 23 | 
11 files changed, 672 insertions, 275 deletions
| diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9f2b419 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,25 @@ + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <http://unlicense.org/> diff --git a/README-es.md b/README-es.md new file mode 100644 index 0000000..f1f428e --- /dev/null +++ b/README-es.md @@ -0,0 +1,41 @@ + +# Bilioteca de internacionalización para Minetest + +Por Diego Martínez (kaeza). +Lanzada bajo Unlicense. Véase `LICENSE.md` para más detalles. + +Éste mod es un intento por proveer soporte para internacionalización +de los mods (algo que a Minetest le falta de momento). + +Si tienes alguna duda/comentario, por favor publica en el +[tema del foro][topic]. Por reporte de errores, use el +[bugtracker][bugtracker] en Github. + +## Cómo usar + +Si eres un jugador regular en busca de textos traducidos, simplemente +[instala][installing_mods] éste mod como cualquier otro. + +El mod trata de detectar tu idioma, pero ya que no hay una forma portable de +hacerlo, prueba varias alternativas: + +* `language` setting in `minetest.conf`. +* `LANGUAGE` environment variable. +* `LANG` environment variable. + +En cualquier caso, el resultado final debería ser el +[Código de idioma ISO 639-1][ISO639-1] del idioma deseado. + +### Desarrolladores + +Si desarrollas mods y estás buscando añadir soporte de internacionalización +a tu mod, ve el fichero `doc/developer.md`. + +### Traductores + +Si eres un traductor, ve el fichero `doc/translator.md`. + +[topic]: https://forum.minetest.net/viewtopic.php?id=4929 +[bugtracker]: https://github.com/minetest-mods/intllib/issues +[installing_mods]: https://wiki.minetest.net/Installing_mods/es +[ISO639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes diff --git a/README-es_UY.md b/README-es_UY.md deleted file mode 100644 index 79ea84d..0000000 --- a/README-es_UY.md +++ /dev/null @@ -1,136 +0,0 @@ - -# Biblioteca de Internacionalización para Minetest - -Por Diego Martínez (kaeza). -Lanzada bajo WTFPL. - -Éste mod es un intento de proveer soporte para internacionalización para otros mods -(lo cual Minetest carece actualmente). - -## Cómo usar - -### Para usuarios finales - -Para usar éste mod, simplemente [instálalo](http://wiki.minetest.net/Installing_Mods) -y habilítalo en la interfaz. - -Éste mod intenta detectar el idioma del usuario, pero ya que no existe una solución -portable para hacerlo, éste intenta varias alternativas, y utiliza la primera -encontrada: - -  * Opción `language` en `minetest.conf`. -  * Si ésta no está definida, usa la variable de entorno `LANG` (ésta está -    siempre definida en SOs como Unix). -  * Si todo falla, usa `en` (lo cual básicamente significa textos sin traducir). - -En todo caso, el resultado final debe ser el In any case, the end result should be the -[Código de Idioma ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) -del idioma deseado. Tenga en cuenta tambien que (de momento) solo los dos primeros -caracteres son usados, así que por ejemplo, las opciones `de_DE.UTF-8`, `de_DE`, -y `de` son iguales. - -Algunos códigos comúnes: `es` para Español, `pt` para Portugués, `fr` para Francés, -`it` para Italiano, `de` para Aleman. - -### Para desarrolladores - -Para habilitar funcionalidad en tu mod, copia el siguiente fragmento de código y pégalo -al comienzo de tus archivos fuente: - -```lua --- Boilerplate to support localized strings if intllib mod is installed. -local S -if minetest.get_modpath("intllib") then -	S = intllib.Getter() -else -	-- Si no requieres patrones de reemplazo (@1, @2, etc) usa ésto: -	S = function(s) return s end - -	-- Si requieres patrones de reemplazo, pero no escapes, usa ésto: -	S = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end - -	-- Usa ésto si necesitas funcionalidad completa: -	S = function(s,a,...)if a==nil then return s end a={a,...}return s:gsub("(@?)@(%(?)(%d+)(%)?)",function(e,o,n,c)if e==""then return a[tonumber(n)]..(o==""and c or"")else return"@"..o..n..c end end) end -end -``` - -Tambien necesitarás depender opcionalmente de intllib. Para hacerlo, añade `intllib?` -a tu archivo `depends.txt`. Ten en cuenta tambien que si intllib no está instalado, -la función `S` es definida para regresar la cadena sin cambios. Ésto se hace para -evitar la necesidad de llenar tu código con montones de `if`s (o similar) para verificar -que la biblioteca está instalada. - -Luego, para cada cadena de texto a traducir en tu código, usa la función `S` -(definida en el fragmento de arriba) para regresar la cadena traducida. Por ejemplo: - -```lua -minetest.register_node("mimod:minodo", { -	-- Cadena simple: -	description = S("My Fabulous Node"), -	-- Cadena con patrones de reemplazo: -	description = S("@1 Car", "Blue"), -	-- ... -}) -``` - -Nota: Las cadenas en el código fuente por lo general deben estar en ingles ya que -es el idioma que más se habla. Es perfectamente posible especificar las cadenas -fuente en español y proveer una traducción al ingles, pero no se recomienda. - -Luego, crea un directorio llamado `locale` dentro del directorio de tu mod, y crea -un archivo "plantilla" (llamado `template.txt` por lo general) con todas las cadenas -a traducir (ver *Formato de archivo de traducciones* más abajo). Los traductores -traducirán las cadenas en éste archivo para agregar idiomas a tu mod. - -### Para traductores - -Para traducir un mod que tenga soporte para intllib al idioma deseado, copia el -archivo `locale/template.txt` a `locale/IDIOMA.txt` (donde `IDIOMA` es el -[Código de Idioma ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) -de tu idioma (`es` para español). - -Abre el archivo en tu editor favorito, y traduce cada línea colocando el texto -traducido luego del signo de igualdad. - -Ver *Formato de archivo de traducciones* más abajo. - -## Formato de archivo de traducciones - -He aquí un ejemplo de archivo de idioma para el español (`es.txt`): - -```cfg -# Un comentario. -# Otro comentario. -Ésta línea es ignorada porque no tiene un signo de igualdad. -Hello, World! = Hola, Mundo! -String with\nnewlines = Cadena con\nsaltos de linea -String with an \= equals sign = Cadena con un signo de \= igualdad -``` - -Archivos de idioma (o traducción) son archivos de texto sin formato que consisten de -líneas con el formato `texto fuente = texto traducido`. El archivo debe ubicarse en el -subdirectorio `locale` del mod, y su nombre debe ser las dos letras del -[Código de Idioma ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) -del lenguaje al cual se desea traducir. - -Los archivos deben usar la codificación UTF-8. - -Las líneas que comienzan en el símbolo numeral (`#`) son comentarios y son ignoradas -por el lector. Tenga en cuenta que los comentarios terminan al final de la línea; -no hay soporte para comentarios multilínea. Las líneas que no contengan un signo -de igualdad (`=`) tambien son ignoradas. - -## Palabras finales - -Gracias por leer hasta aquí. -Si tienes algún comentario/sugerencia, por favor publica en el -[tema en los foros](https://forum.minetest.net/viewtopic.php?id=4929). Para -reportar errores, usa el [rastreador](https://github.com/minetest-mods/intllib/issues/new) -en Github. - -¡Que se hagan las traducciones! :P - -\-- - -Suyo, -Kaeza @@ -2,142 +2,42 @@  # Internationalization Lib for Minetest  By Diego Martínez (kaeza). -Released as WTFPL. +Released under Unlicense. See `LICENSE.md` for details.  This mod is an attempt at providing internationalization support for mods  (something Minetest currently lacks). -## How to use - -### For end users - -To use this mod, just [install it](http://wiki.minetest.net/Installing_Mods) -and enable it in the GUI. - -The mod tries to detect the user's language, but since there's currently no -portable way to do this, it tries several alternatives, and uses the first one -found: - -  * `language` setting in `minetest.conf`. -  * If that's not set, it uses the `LANG` environment variable (this is -    always set on Unix-like OSes). -  * If all else fails, uses `en` (which basically means untranslated strings). - -In any case, the end result should be the -[ISO 639-1 Language Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) -of the desired language. Also note that (currently) 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. - -Some common codes are `es` for Spanish, `pt` for Portuguese, `fr` for French, -`it` for Italian, `de` for German. - -### For mod developers - -In order to enable it for your mod, copy the following code snippet and paste -it at the beginning of your source file(s): - -```lua --- Boilerplate to support localized strings if intllib mod is installed. -local S -if minetest.get_modpath("intllib") then -	S = intllib.Getter() -else -	-- If you don't use insertions (@1, @2, etc) you can use this: -	S = function(s) return s end - -	-- If you use insertions, but not insertion escapes this will work: -	S = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end - -	-- Use this if you require full functionality -	S = function(s,a,...)if a==nil then return s end a={a,...}return s:gsub("(@?)@(%(?)(%d+)(%)?)",function(e,o,n,c)if e==""then return a[tonumber(n)]..(o==""and c or"")else return"@"..o..n..c end end) end -end -``` - -You will also need to optionally depend on intllib, to do so add `intllib?` to -an 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: - -```lua -minetest.register_node("mymod:mynode", { -	-- Simple string: -	description = S("My Fabulous Node"), -	-- String with insertions: -	description = S("@1 Car", "Blue"), -	-- ... -}) -``` - -Then, you create a `locale` directory inside your mod directory, and create -a "template" file (by convention, named `template.txt`) with all the -translatable strings (see *Locale file format* below). Translators will -translate the strings in this file to add languages to your mod. - -### For translators - -To translate an intllib-supporting mod to your desired language, copy the -`locale/template.txt` file to `locale/LANGUAGE.txt` (where `LANGUAGE` is the -[ISO 639-1 Language Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) -of your language. - -Open up the new file in your favorite editor, and translate each line putting -the translated text after the equals sign. - -See *Locale file format* below for more information about the file format. - -## Locale file format - -Here's an example for a Spanish locale file (`es.txt`): - -```cfg -# A comment. -# Another comment. -This line is ignored since it has no equals sign. -Hello, World! = Hola, Mundo! -String with\nnewlines = Cadena con\nsaltos de linea -String with an \= equals sign = Cadena con un signo de \= igualdad -``` +Should you have any comments/suggestions, please post them in the +[forum topic][topic]. For bug reports, use the [bug tracker][bugtracker] +on Github. -Locale (or translation) files are plain text files consisting of lines of the -form `source text = translated text`. The file must reside in the mod's `locale` -subdirectory, and must be named after the two-letter -[ISO 639-1 Language Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) -of the language you want to support. +## How to use -The translation files should use the UTF-8 encoding. +If you are a regular player looking for translated texts, just +[install][installing_mods] this mod like any other one, then enable it +in the GUI. -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. Lines without an equals sign are -also ignored. +The mod tries to detect your language, but since there's currently no +portable way to do this, it tries several alternatives: -Characters that are considered "special" can be "escaped" so they are taken -literally. There are also several escape sequences that can be used: +* `language` setting in `minetest.conf`. +* `LANGUAGE` environment variable. +* `LANG` environment variable. +* If all else fails, uses `en`. -  * Any of `#`, `=` can be escaped to take them literally. The `\#` -    sequence is useful if your source text begins with `#`. -  * The common escape sequences `\n` and `\t`, meaning newline and -    horizontal tab respectively. -  * The special `\s` escape sequence represents the space character. It -    is mainly useful to add leading or trailing spaces to source or -    translated texts, as these spaces would be removed otherwise. +In any case, the end result should be the [ISO 639-1 Language Code][ISO639-1] +of the desired language. -## Final words +### Mod developers -Thanks for reading up to this point. -Should you have any comments/suggestions, please post them in the -[forum topic](https://forum.minetest.net/viewtopic.php?id=4929). For bug -reports, use the [bug tracker](https://github.com/minetest-mods/intllib/issues/new) -on Github. +If you are a mod developer looking to add internationalization support to +your mod, see `doc/developer.md`. -Let there be translated texts! :P +### Translators -\-- +If you are a translator, see `doc/translator.md`. -Yours Truly, -Kaeza +[topic]: https://forum.minetest.net/viewtopic.php?id=4929 +[bugtracker]: https://github.com/minetest-mods/intllib/issues +[installing_mods]: https://wiki.minetest.net/Installing_mods +[ISO639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes diff --git a/doc/developer.md b/doc/developer.md new file mode 100644 index 0000000..9408b1a --- /dev/null +++ b/doc/developer.md @@ -0,0 +1,96 @@ + +# Intllib developer documentation + +In order to enable it for your mod, copy some boilerplate into your +source file(s). What you need depends on what you want to support. + +There are now two main interfaces: one using the old plain text file method, +and one using the new support for [gettext][gettext] message catalogs (`.mo`). +Read below for details on each one. + +You will also need to optionally depend on intllib, to do so add `intllib?` +to an empty line in your `depends.txt`. Also note that if intllib is not +installed, the getter functions are defined so they return 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. + +## New interface + +You will need to copy the file `lib/intllib.lua` into the root directory of +your mod, then include this boilerplate code in files needing localization: + +    -- Load support for intllib. +    local MP = minetest.get_modpath(minetest.get_current_modname()) +    local S, NS = dofile(MP.."/intllib.lua") + +Use the usual gettext tools (`xgettext`, `msgfmt`, etc.), to generate your +catalog files in a directory named `locale`. + +Note: Drop the `.mo` file directly as `locale/$lang.mo`. **Not** in +`locale/$lang/LC_MESSAGES/$domain.mo`! + +You should also provide the source `.po` and `.pot` files. + +### Basic workflow + +This is the basic workflow for working with [gettext][gettext] + +Each time you have new strings to be translated, you should do the following: + +    cd /path/to/mod +    /path/to/intllib/tools/xgettext.sh file1.lua file2.lua ... + +The script will create a directory named `locale` if it doesn't exist yet, +and will generate the file `template.pot`. If you already have translations, +the script will proceed to update all of them with the new strings. + +The script passes some options to the real `xgettext` that should be enough +for most cases. You may specify other options if desired: + +    xgettext.sh -o file.pot --keyword=blargh:4,5 a.lua b.lua ... + +NOTE: There's also a Windows batch file `xgettext.bat` for Windows users, +but you will need to install the gettext command line tools separately. See +the top of the file for configuration. + +Once a translator submits an updated translation, you should run the `msgfmt` +tool: + +    msgfmt locale/ll_CC.po -o locale/ll_CC.mo + +## Old interface + +You will need this boilerplate code: + +    -- Boilerplate to support localized strings if intllib mod is installed. +    local S +    if minetest.get_modpath("intllib") then +        S = intllib.Getter() +    else +        -- If you don't use insertions (@1, @2, etc) you can use this: +        S = function(s) return s end +     +        -- If you use insertions, but not insertion escapes this will work: +        S = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end +     +        -- Use this if you require full functionality +        S = function(s,a,...)if a==nil then return s end a={a,...}return s:gsub("(@?)@(%(?)(%d+)(%)?)",function(e,o,n,c)if e==""then return a[tonumber(n)]..(o==""and c or"")else return"@"..o..n..c end end) end +    end + +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", { +        -- Simple string: +        description = S("My Fabulous Node"), +        -- String with insertions: +        description = S("@1 Car", "Blue"), +        -- ... +    }) + +Then, you create a `locale` directory inside your mod directory, and create +a "template" file (by convention, named `template.txt`) with all the +translatable strings (see *Locale file format* below). Translators will +translate the strings in this file to add languages to your mod. + +[gettext]: https://www.gnu.org/software/gettext/ diff --git a/doc/translator.md b/doc/translator.md new file mode 100644 index 0000000..3c278e8 --- /dev/null +++ b/doc/translator.md @@ -0,0 +1,20 @@ + +# Intllib translator documentation + +#### New interface + +Use your favorite tools to edit the `.po` files. + +#### Old interface + +To translate an intllib-supporting mod to your desired language, copy the +`locale/template.txt` file to `locale/LANGUAGE.txt` (where `LANGUAGE` is the +[ISO 639-1 Language Code][ISO639-1] of your language. + +Open up the new file in your favorite editor, and translate each line putting +the translated text after the equals sign. + +See `localefile.md` for more information about the file format. + +[gettext]: https://www.gnu.org/software/gettext/ +[ISO639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes diff --git a/gettext.lua b/gettext.lua new file mode 100644 index 0000000..6f3a1cb --- /dev/null +++ b/gettext.lua @@ -0,0 +1,288 @@ + +local strfind, strsub, strrep = string.find, string.sub, string.rep +local strmatch, strgsub = string.match, string.gsub +local floor = math.floor + +local function split(str, sep) +	local pos, endp = 1, #str+1 +	return function() +		if (not pos) or pos > endp then return end +		local s, e = strfind(str, sep, pos, true) +		local part = strsub(str, pos, s and s-1) +		pos = e and e + 1 +		return part +	end +end + +local function trim(str) +	return strmatch(str, "^%s*(.-)%s*$") +end + +local escapes = { n="\n", r="\r", t="\t" } + +local function unescape(str) +	return (strgsub(str, "(\\+)([nrt]?)", function(bs, c) +		local bsl = #bs +		local realbs = strrep("\\", bsl/2) +		if bsl%2 == 1 then +			c = escapes[c] +		end +		return realbs..c +	end)) +end + +local function parse_po(str) +	local state, msgid, msgid_plural, msgstrind +	local texts = { } +	local lineno = 0 +	local function perror(msg) +		return error(msg.." at line "..lineno) +	end +	for line in split(str, "\n") do repeat +		lineno = lineno + 1 +		line = trim(line) + +		if line == "" or strmatch(line, "^#") then +			state, msgid, msgid_plural = nil, nil, nil +			break -- continue +		end + +		local mid = strmatch(line, "^%s*msgid%s*\"(.*)\"%s*$") +		if mid then +			if state == "id" then +				return perror("unexpected msgid") +			end +			state, msgid = "id", unescape(mid) +			break -- continue +		end + +		mid = strmatch(line, "^%s*msgid_plural%s*\"(.*)\"%s*$") +		if mid then +			if state ~= "id" then +				return perror("unexpected msgid_plural") +			end +			state, msgid_plural = "idp", unescape(mid) +			break -- continue +		end + +		local ind, mstr = strmatch(line, +				"^%s*msgstr([0-9%[%]]*)%s*\"(.*)\"%s*$") +		if ind then +			if not msgid then +				return perror("missing msgid") +			elseif ind == "" then +				msgstrind = 0 +			elseif strmatch(ind, "%[[0-9]+%]") then +				msgstrind = tonumber(strsub(ind, 2, -2)) +			else +				return perror("malformed msgstr") +			end +			texts[msgid] = texts[msgid] or { } +			if msgid_plural then +				texts[msgid_plural] = texts[msgid] +			end +			texts[msgid][msgstrind] = unescape(mstr) +			state = "str" +			break -- continue +		end + +		mstr = strmatch(line, "^%s*\"(.*)\"%s*$") +		if mstr then +			if state == "id" then +				msgid = msgid..unescape(mstr) +				break -- continue +			elseif state == "idp" then +				msgid_plural = msgid_plural..unescape(mstr) +				break -- continue +			elseif state == "str" then +				local text = texts[msgid][msgstrind] +				texts[msgid][msgstrind] = text..unescape(mstr) +				break -- continue +			end +		end + +		return perror("malformed line") + +	until true end -- end for + +	return texts +end + +local M = { } + +local domains = { } +local dgettext_cache = { } +local dngettext_cache = { } +local langs + +local function detect_languages() +	if langs then return langs end + +	langs = { } + +	local function addlang(l) +		local sep +		langs[#langs+1] = l +		sep = strfind(l, ".", 1, true) +		if sep then +			l = strsub(l, 1, sep-1) +			langs[#langs+1] = l +		end +		sep = strfind(l, "_", 1, true) +		if sep then +			langs[#langs+1] = strsub(l, 1, sep-1) +		end +	end + +	local v + +	v = rawget(_G, "minetest") and minetest.setting_get("language") +	if v and v~="" then +		addlang(v) +	end + +	v = os.getenv("LANGUAGE") +	if v then +		for item in split(v, ":") do +			addlang(item) +		end +	end + +	v = os.getenv("LANG") +	if v then +		addlang(v) +	end + +	return langs +end + +local function warn(msg) +	if rawget(_G, "minetest") then +		minetest.log("warning", msg) +	else +		io.stderr:write("WARNING: ", msg, "\n") +	end +end + +-- hax! +-- This function converts a C expression to an equivalent Lua expression. +-- It handles enough stuff to parse the `Plural-Forms` header correctly. +-- Note that it assumes the C expression is valid to begin with. +local function compile_plural_forms(str) +	local plural = strmatch(str, "plural=([^;]+);?$") +	local function replace_ternary(str) +		local c, t, f = strmatch(str, "^(.-)%?(.-):(.*)") +		if c then +			return ("__if(" +					..replace_ternary(c) +					..","..replace_ternary(t) +					..","..replace_ternary(f) +					..")") +		end +		return str +	end +	plural = replace_ternary(plural) +	plural = strgsub(plural, "&&", " and ") +	plural = strgsub(plural, "||", " or ") +	plural = strgsub(plural, "!=", "~=") +	plural = strgsub(plural, "!", " not ") +	local f, err = loadstring([[ +		local function __if(c, t, f) +			if c and c~=0 then return t else return f end +		end +		local function __f(n) +			return (]]..plural..[[) +		end +		return (__f(...)) +	]]) +	if not f then return nil, err end +	local env = { } +	env._ENV, env._G = env, env +	setfenv(f, env) +	return function(n) +		local v = f(n) +		if type(v) == "boolean" then +			-- Handle things like a plain `n != 1` +			v = v and 1 or 0 +		end +		return v +	end +end + +local function parse_headers(str) +	local headers = { } +	for line in split(str, "\n") do +		local k, v = strmatch(line, "^([^:]+):%s*(.*)") +		if k then +			headers[k] = v +		end +	end +	return headers +end + +local function load_catalog(filename) +	local f, data, err + +	local function bail(msg) +		warn(msg..(err and ": ")..(err or "")) +		return nil +	end + +	f, err = io.open(filename, "rb") +	if not f then +		return --bail("failed to open catalog") +	end + +	data, err = f:read("*a") + +	f:close() + +	if not data then +		return bail("failed to read catalog") +	end + +	data, err = parse_po(data) +	if not data then +		return bail("failed to parse catalog") +	end + +	err = nil +	local hdrs = data[""] +	if not (hdrs and hdrs[0]) then +		print(dump(hdrs)) +		return bail("catalog has no headers") +	end + +	hdrs = parse_headers(hdrs[0]) + +	local pf = hdrs["Plural-Forms"] +	if not pf then +		return bail("failed to load catalog:" +				.." catalog has no Plural-Forms header") +	end + +	data.plural_index, err = compile_plural_forms(pf) +	if not data.plural_index then +		return bail("failed to compile plural forms") +	end + +	--warn("loaded: "..filename) + +	return data +end + +function M.load_catalogs(path) +	detect_languages() + +	local cats = { } +	for _, lang in ipairs(langs) do +		local cat = load_catalog(path.."/"..lang..".po") +		if cat then +			cats[#cats+1] = cat +		end +	end + +	return cats +end + +return M @@ -21,6 +21,22 @@ if not (LANG and (LANG ~= "")) then LANG = "en" end  local INS_CHAR = intllib.INSERTION_CHAR  local insertion_pattern = "("..INS_CHAR.."?)"..INS_CHAR.."(%(?)(%d+)(%)?)" +local function do_replacements(str, ...) +	local args = {...} +	-- Outer parens discard extra return values +	return (str:gsub(insertion_pattern, function(escape, open, num, close) +		if escape == "" then +			local replacement = tostring(args[tonumber(num)]) +			if open == "" then +				replacement = replacement..close +			end +			return replacement +		else +			return INS_CHAR..open..num..close +		end +	end)) +end +  local function make_getter(msgstrs)  	return function(s, ...)  		local str @@ -33,24 +49,12 @@ local function make_getter(msgstrs)  		if select("#", ...) == 0 then  			return str  		end -		local args = {...} -		str = str:gsub(insertion_pattern, function(escape, open, num, close) -			if escape == "" then -				local replacement = tostring(args[tonumber(num)]) -				if open == "" then -					replacement = replacement..close -				end -				return replacement -			else -				return INS_CHAR..open..num..close -			end -		end) -		return str +		return do_replacements(str, ...)  	end  end -function intllib.Getter(modname) +local function Getter(modname)  	modname = modname or minetest.get_current_modname()  	if not intllib.getters[modname] then  		local msgstr = intllib.get_strings(modname) @@ -60,6 +64,64 @@ function intllib.Getter(modname)  end +function intllib.Getter(modname) +	minetest.log("deprecated", "intllib.Getter is deprecated." +			.."Please use intllib.make_gettext_pair instead.") +	return Getter(modname) +end + + +local gettext = dofile(minetest.get_modpath("intllib").."/gettext.lua") + + +local function catgettext(catalogs, msgid) +	for _, cat in ipairs(catalogs) do +		local msgstr = cat and cat[msgid] +		if msgstr then +			return msgstr[0] +		end +	end +end + +local function catngettext(catalogs, msgid, msgid_plural, n) +	n = math.floor(n) +	for i, cat in ipairs(catalogs) do +		print(i, dump(cat)) +		local msgstr = cat and cat[msgid] +		if msgstr then +			local index = cat.plural_index(n) +			print("catngettext:", index, msgstr[index]) +			return msgstr[index] +		end +	end +	return n==1 and msgid or msgid_plural +end + + +local gettext_getters = { } +function intllib.make_gettext_pair(modname) +	modname = modname or minetest.get_current_modname() +	if gettext_getters[modname] then +		return unpack(gettext_getters[modname]) +	end +	local localedir = minetest.get_modpath(modname).."/locale" +	local catalogs = gettext.load_catalogs(localedir) +	local getter = Getter(modname) +	local function gettext(msgid, ...) +		local msgstr = (catgettext(catalogs, msgid) +				or getter(msgid)) +		return do_replacements(msgstr, ...) +	end +	local function ngettext(msgid, msgid_plural, n, ...) +		local msgstr = (catngettext(catalogs, msgid, msgid_plural, n) +				or getter(msgid)) +		return do_replacements(msgstr, ...) +	end +	gettext_getters[modname] = { gettext, ngettext } +	return gettext, ngettext +end + +  local function get_locales(code)  	local ll, cc = code:match("^(..)_(..)")  	if ll then diff --git a/lib/intllib.lua b/lib/intllib.lua new file mode 100644 index 0000000..6669d72 --- /dev/null +++ b/lib/intllib.lua @@ -0,0 +1,45 @@ + +-- Fallback functions for when `intllib` is not installed. +-- Code released under Unlicense <http://unlicense.org>. + +-- Get the latest version of this file at: +--   https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua + +local function format(str, ...) +	local args = { ... } +	local function repl(escape, open, num, close) +		if escape == "" then +			local replacement = tostring(args[tonumber(num)]) +			if open == "" then +				replacement = replacement..close +			end +			return replacement +		else +			return "@"..open..num..close +		end +	end +	return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl)) +end + +local gettext, ngettext +if minetest.get_modpath("intllib") then +	if intllib.make_gettext_pair then +		-- New method using gettext. +		gettext, ngettext = intllib.make_gettext_pair() +	else +		-- Old method using text files. +		gettext = intllib.Getter() +	end +end + +-- Fill in missing functions. + +gettext = gettext or function(msgid, ...) +	return format(msgid, ...) +end + +ngettext = ngettext or function(msgid, msgid_plural, n, ...) +	return format(n==1 and msgid or msgid_plural, ...) +end + +return gettext, ngettext diff --git a/tools/xgettext.bat b/tools/xgettext.bat new file mode 100644 index 0000000..18403db --- /dev/null +++ b/tools/xgettext.bat @@ -0,0 +1,33 @@ +@echo off
 +setlocal
 +
 +set me=%~n0
 +
 +rem # Uncomment the following line if gettext is not in your PATH.
 +rem # Value must be absolute and end in a backslash.
 +rem set gtprefix=C:\path\to\gettext\bin\
 +
 +if "%1" == "" (
 +	echo Usage: %me% FILE... 1>&2
 +	exit 1
 +)
 +
 +set xgettext=%gtprefix%xgettext.exe
 +set msgmerge=%gtprefix%msgmerge.exe
 +
 +md locale > nul 2>&1
 +echo Generating template... 1>&2
 +echo %xgettext% --from-code=UTF-8 -kS -kNS:1,2 -k_ -o locale/template.pot %*
 +%xgettext% --from-code=UTF-8 -kS -kNS:1,2 -k_ -o locale/template.pot %*
 +if %ERRORLEVEL% neq 0 goto done
 +
 +cd locale
 +
 +for %%f in (*.po) do (
 +	echo Updating %%f... 1>&2
 +	%msgmerge% --update %%f template.pot
 +)
 +
 +echo DONE! 1>&2
 +
 +:done
 diff --git a/tools/xgettext.sh b/tools/xgettext.sh new file mode 100755 index 0000000..6de353c --- /dev/null +++ b/tools/xgettext.sh @@ -0,0 +1,23 @@ +#! /bin/bash + +me=$(basename "${BASH_SOURCE[0]}"); + +if [[ $# -lt 1 ]]; then +	echo "Usage: $me FILE..." >&2; +	exit 1; +fi + +mkdir -p locale; +echo "Generating template..." >&2; +xgettext --from-code=UTF-8 -kS -kNS:1,2 -k_ \ +		-o locale/template.pot "$@" \ +		|| exit; + +cd locale; + +for file in *.po; do +	echo "Updating $file..." >&2; +	msgmerge --update "$file" template.pot; +done + +echo "DONE!" >&2; | 
