summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Martínez <kaeza@users.noreply.github.com>2017-01-21 01:04:03 -0300
committerDiego Martínez <kaeza@users.noreply.github.com>2017-01-24 00:24:57 -0300
commitb2551f6a2209b8a11b42834cb0d63f5c03a2b95f (patch)
tree93e1ac391a3146431b273c94805f543b63b64fb1
parent4e067ec21906e9a27ec704dd5f34297b2592d6de (diff)
Add support for gettext message catalogs.
-rw-r--r--LICENSE.md25
-rw-r--r--README-es.md41
-rw-r--r--README-es_UY.md136
-rw-r--r--README.md150
-rw-r--r--doc/developer.md96
-rw-r--r--doc/translator.md20
-rw-r--r--gettext.lua288
-rw-r--r--init.lua90
-rw-r--r--lib/intllib.lua45
-rw-r--r--tools/xgettext.bat33
-rwxr-xr-xtools/xgettext.sh23
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
diff --git a/README.md b/README.md
index 185ca1d..de7cfa3 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/init.lua b/init.lua
index 79d4c31..ee6d1a9 100644
--- a/init.lua
+++ b/init.lua
@@ -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;