mirror of
https://github.com/minetest-mods/intllib.git
synced 2025-01-23 16:30:18 +01:00
[WIP] Add support for gettext message catalogs.
This commit is contained in:
parent
4e067ec219
commit
6a4eea7a9c
25
LICENSE.md
Normal file
25
LICENSE.md
Normal file
@ -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/>
|
41
README-es.md
Normal file
41
README-es.md
Normal file
@ -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
|
136
README-es_UY.md
136
README-es_UY.md
@ -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
|
160
README.md
160
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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
The translation files should use the UTF-8 encoding.
|
||||
|
||||
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.
|
||||
|
||||
Characters that are considered "special" can be "escaped" so they are taken
|
||||
literally. There are also several escape sequences that can be used:
|
||||
|
||||
* 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.
|
||||
|
||||
## Final words
|
||||
|
||||
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)
|
||||
[forum topic][topic]. For bug reports, use the [bug tracker][bugtracker]
|
||||
on Github.
|
||||
|
||||
Let there be translated texts! :P
|
||||
## How to use
|
||||
|
||||
\--
|
||||
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.
|
||||
|
||||
Yours Truly,
|
||||
Kaeza
|
||||
The mod tries to detect your language, but since there's currently no
|
||||
portable way to do this, it tries several alternatives:
|
||||
|
||||
* `language` setting in `minetest.conf`.
|
||||
* `LANGUAGE` environment variable.
|
||||
* `LANG` environment variable.
|
||||
* If all else fails, uses `en`.
|
||||
|
||||
In any case, the end result should be the [ISO 639-1 Language Code][ISO639-1]
|
||||
of the desired language.
|
||||
|
||||
### Mod developers
|
||||
|
||||
If you are a mod developer looking to add internationalization support to
|
||||
your mod, see `doc/developer.md`.
|
||||
|
||||
### Translators
|
||||
|
||||
If you are a translator, see `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
|
||||
[ISO639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
||||
|
96
doc/developer.md
Normal file
96
doc/developer.md
Normal file
@ -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/
|
20
doc/translator.md
Normal file
20
doc/translator.md
Normal file
@ -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
|
288
gettext.lua
Normal file
288
gettext.lua
Normal file
@ -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
|
90
init.lua
90
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
|
||||
|
45
lib/intllib.lua
Normal file
45
lib/intllib.lua
Normal file
@ -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
|
33
tools/xgettext.bat
Normal file
33
tools/xgettext.bat
Normal file
@ -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
|
23
tools/xgettext.sh
Executable file
23
tools/xgettext.sh
Executable file
@ -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;
|
Loading…
Reference in New Issue
Block a user