From 07f5afb1af15c525bb411b62b1521e7ce00398ec Mon Sep 17 00:00:00 2001 From: Paul Donald Date: Thu, 15 Feb 2024 00:44:01 +0100 Subject: [PATCH] build: update build/i18n-* tools modify i18n-add-language.sh so it can: - be run from any path - bootstrap any (or all) po folder(s) with existing language(s) - (partially) update luci.mk with new languages deprecate build/i18n-init.sh Signed-off-by: Paul Donald (cherry picked from commit b98d8c526e05d658856a83c469f79957223fbfd6) --- build/i18n-add-language.sh | 88 +++++++++++++++++-------- build/i18n-init.sh | 4 ++ build/i18n-scan.pl | 2 +- build/i18n-update.pl | 2 +- build/mkbasepot.sh | 6 +- docs/i18n.md | 130 ++++++++++++++++++++++++++++++++----- luci.mk | 11 ++-- 7 files changed, 190 insertions(+), 53 deletions(-) diff --git a/build/i18n-add-language.sh b/build/i18n-add-language.sh index b4a852efb7..94568000e1 100755 --- a/build/i18n-add-language.sh +++ b/build/i18n-add-language.sh @@ -1,37 +1,71 @@ #!/usr/bin/env bash -LANG=$1 +LANGS=$@ +if [ "$#" -eq 0 ]; then + echo $0 "adds i18n catalogue(s) in po/ folders (luci-app-*, luci-mod-*, etc) for each LUCI_LANG.* in luci.mk" + echo "Hint: run in the root of the luci repo or in your luci-app-* folder." -case "$LANG" in - [a-z][a-z]|[a-z][a-z][_-][A-Za-z][A-Za-z]*) : ;; - *) - echo "Usage: $0 \n" >&2 - exit 1 - ;; -esac + # get existing language codes from luci.mk + language_codes=$(grep -o 'LUCI_LANG\.[a-zA-Z]*' $(dirname "$0")/../luci.mk | cut -d '.' -f 2 | sort -u) + LANGS=$language_codes -ADDED=0 +else + for LANG in $LANGS; do + case "$LANG" in + [a-z][a-z]|[a-z][a-z][_-][A-Za-z][A-Za-z]*) : ;; + *) + echo $0 "adds i18n catalogues in each folder (luci-app-*, luci-mod-*, etc)." + echo "Usage: $0 [ ...]" >&2 + exit 1 + ;; + esac + done +fi -for podir in ./*/*/po; do - [ -d "$podir/templates" ] || continue +ADDED=false - mkdir "$podir/$LANG" - for catalog in $(cd "$podir/templates"; echo *.pot); do - if [ -f "$podir/templates/$catalog" -a ! -f "$podir/$LANG/${catalog%.pot}.po" ]; then - msginit --no-translator -l "$LANG" -i "$podir/templates/$catalog" -o "$podir/$LANG/${catalog%.pot}.po" - git add "$podir/$LANG/${catalog%.pot}.po" - ADDED=$((ADDED + 1)) - fi +for podir in $(find . -type d -name "po"); do + [ -d "$podir/templates" ] || continue + for LANG in $LANGS; do + # if "$podir/$LANG" doesn't exist, mkdir + [ -d "$podir/$LANG" ] || mkdir "$podir/$LANG" + for catalog in $(cd "$podir/templates"; echo *.pot); do + if [ -f "$podir/templates/$catalog" -a ! -f "$podir/$LANG/${catalog%.pot}.po" ]; then + msginit --no-translator -l "$LANG" -i "$podir/templates/$catalog" -o "$podir/$LANG/${catalog%.pot}.po" + git add "$podir/$LANG/${catalog%.pot}.po" + ADDED=true + fi + done done done -if [ $ADDED -gt 0 ]; then - echo "" - echo "Added $ADDED new translation catalogs for language '$LANG'." - echo "Please also edit 'luci.mk' and add" - echo "" - echo " LUCI_LANG.$LANG=Native Language Name" - echo "" - echo "to properly package the translation files." - echo "" +start_marker="^#LUCI_LANG_START$" +end_marker="^#LUCI_LANG_END$" + +if [ $ADDED ]; then + for LANG in $LANGS; do + if [[ $language_codes != *"$LANG"* ]]; then + + # Read the contents of the luci.mk file + file_content=$(cat "$(dirname "$0")/../luci.mk") + + # Extract the section between start and end markers + section=$(awk -v start="$start_marker" -v end="$end_marker" ' + $0 ~ start {RS="\n"; printf ""; flag=1; next} + $0 ~ end {flag=0} flag' <<< "$file_content") + + # Add the new language code to the section + section+="\nLUCI_LANG.$LANG=New language" + # Sort the section and remove duplicates + updated_content=$(echo -e "$section" | sort -u | sed -E "/$start_marker/,/$end_marker/{ /$start_marker/{p; r /dev/stdin + }; /$end_marker/p; d + }" $(dirname "$0")/../luci.mk) + + # Write the updated content back to the .mk file + echo "$updated_content" > $(dirname "$0")/../luci.mk + + echo "Be sure to update the new language name in $(dirname "$0")/../luci.mk" + + fi + done fi diff --git a/build/i18n-init.sh b/build/i18n-init.sh index 94abdfcd56..de8626163d 100755 --- a/build/i18n-init.sh +++ b/build/i18n-init.sh @@ -3,6 +3,10 @@ PATTERN=$1 SCM= +echo $0 "initialises po/ i18n catalogues in empty language sub-folders." +echo $0 "is deprecated and may be removed in the future." +echo "Hint: run i18n-add-language.sh instead." + [ -d .svn ] && SCM="svn" git=$( command -v git 2>/dev/null ) [ "$git" ] && "$git" status >/dev/null && SCM="git" diff --git a/build/i18n-scan.pl b/build/i18n-scan.pl index 0374b6d872..ffa1ca9f21 100755 --- a/build/i18n-scan.pl +++ b/build/i18n-scan.pl @@ -234,7 +234,7 @@ waitpid $msguniq_pid, 0; while (@pot > 0) { my $line = shift @pot; - # Reorder the location comments in a detemrinistic way to + # Reorder the location comments in a deterministic way to # reduce SCM noise when frequently updating templates. if ($line =~ m!^#: !) { my @locs = ($line); diff --git a/build/i18n-update.pl b/build/i18n-update.pl index c82b4fe3dd..e97e3d2f35 100755 --- a/build/i18n-update.pl +++ b/build/i18n-update.pl @@ -52,7 +52,7 @@ my @dirs; if( ! $source ) { - @dirs = glob("./*/*/po/"); + @dirs = glob("./*/*/po"); } else { diff --git a/build/mkbasepot.sh b/build/mkbasepot.sh index 0f9247536b..ec0b1e6207 100755 --- a/build/mkbasepot.sh +++ b/build/mkbasepot.sh @@ -8,9 +8,9 @@ echo -n "Updating modules/luci-base/po/templates/base.pot ... " ./build/i18n-scan.pl \ - modules/luci-base/ modules/luci-compat/ modules/luci-mod-admin-full/ \ - modules/luci-mod-network modules/luci-mod-status modules/luci-mod-system/ \ - protocols/ themes/ \ + modules/luci-base modules/luci-compat modules/luci-mod-admin-full \ + modules/luci-mod-network modules/luci-mod-status modules/luci-mod-system \ + protocols themes \ > modules/luci-base/po/templates/base.pot echo "done" diff --git a/docs/i18n.md b/docs/i18n.md index 226a406c2a..471eb88f1e 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -1,19 +1,117 @@ -# General -Translations are saved in the folder po/ for each module and application. You find the reference in po/templates/.pot. The actual translation files can be found at po/[lang]/[package].po . +# Internationalization (i18n) -In order to use the commands below you need to have the _gettext'' utilities (''msgcat'', ''msgfmt'', ''msgmerge_) installed on your system. +See [online wiki](https://github.com/openwrt/luci/wiki/i18n) for latest version. -# Rebuild po files +## Use translation function + +### Translations in JavaScript + +Wrap translatable strings with `_()` e.g. `_('string to translate')` and the `i18n-scan.pl` and friends will correctly identify these strings as they do with all the existing translations. + +If you have multi line strings you can split them with concatenation: +```js +var mystr = _('this string will translate ' + + 'correctly even though it is ' + + 'a multi line string!'); +``` + +You may also use line continuations `\` syntax: + +```js +var mystr = _('this string will translate \ + correctly even though it is \ + a multi line string'); +``` + +Usually if you have multiple sentences you may need to use a line break then use the `
` HTML tag: +```js +var mystr = _('Port number.
' + + 'E.g. 80 for HTTP'); +``` + +To simplify a job for translators it may be better to split into separate keys without the `
`: +```js +var mystr = _('Port number.') + '
' + + _('E.g. 80 for HTTP'); +``` +Please use `
` and **not** `
` or `
`. + +If you have a link inside a translation then try to move its attributes out of a translation key like: +```js +var mystr = _('For further information check the wiki') + .format('href="https://openwrt.org/docs/" target="_blank" rel="noreferrer"') +``` +This will generate a full link with HTML `For further information check the wiki`. The `noreferrer` is important when making a link that is opened in a new tab (`target="_blank"`). + +### Translations in LuCI lua+html templates +Use the `<%: text to translate %>` as documented on [Templates](./Templates.md) + +### Translations in Lua controller code and Lua CBIs +As hinted at in the Templates doc, the `%:` is actually invoking a `translate()` function. +In most controller contexts, this is already available for you, but if necessary, is available for include in `luci.i18n.translate` + + +## Translation files +Translations are saved in the folder `po/` within each individual LuCI component directory, e.g. `applications/luci-app-acl/po/`. +You find the reference in `po/templates/.pot`. +The actual translation files can be found at `po/[lang]/[package].po`. + +In order to use the commands below you need to have the `gettext` utilities (`msgcat`, `msgfmt`, `msgmerge`) installed on your system. +On Debian/Ubuntu you can install with `sudo apt install gettext`. + +### Initialize po files + +When you add or update an app, simply run from your app folder: + + ../../build/i18n-add-language.sh + +This creates the skeleton po files for all existing languages open for translation for your app. + +Or from the luci repo root: + + ./build/i18n-add-language.sh + +This creates the skeleton po files for all existing languages open for translation for all sub-folders. + +### Rebuild po files If you want to rebuild the translations after you made changes to a package this is an easy way: - - ./build/i18n-scan.pl applications/[application] > applications/[application]/po/templates/[application_basename].pot - ./build/i18n-update.pl applications/[application]/po - - Example: - ./build/i18n-scan.pl applications/luci-app-firewall > applications/luci-app-firewall/po/templates/firewall.pot - ./build/i18n-update.pl applications/luci-app-firewall/po - (note that the directory argument can be omitted for i18n-update.pl to update all apps) - -*Note:* Some packages share translation files, in this case you need to scan through all their folders. The first command from above should then be: - - ./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].pot + + ./build/i18n-scan.pl applications/[application] > applications/[application]/po/templates/[application_basename].pot + ./build/i18n-update.pl applications/[application]/po + +Example: + + ./build/i18n-scan.pl applications/luci-app-acl > applications/luci-app-acl/po/templates/acl.pot + ./build/i18n-update.pl applications/luci-app-acl/po + +Note that the directory argument can be omitted for `i18n-update.pl` to update all apps. + +Some packages share translation files, in this case you need to scan through all their folders. +The first command from above should then be: + + ./build/i18n-scan.pl applications/[package-1] applications/[package-2] applications/[package-n] > [location of shared template]/[application].pot + +*Note:* The translation catalog for the base system covers multiple components, use the following commands to update it: + + ./build/mkbasepot.sh + ./build/i18n-update.pl + +### LMO files +The `*.po` files are big so Luci needs them in a compact compiled [LMO format](./LMO.md). +Luci reads `*.lmo` translations from `/usr/lib/lua/luci/i18n/` folder. +E.g. `luci-app-acl` has an Arabic translation in `luci-i18n-acl-ar` package that installs `/usr/lib/lua/luci/i18n/acl.ar.lmo` file. + +In order to quickly convert a single `.po` file to `.lmo` file for testing on the target system use the `po2lmo` utility. +You will need to compile it from the `luci-base` module: + + $ cd modules/luci-base/src/ + $ make po2lmo + $ ./po2lmo + Usage: ./po2lmo input.po output.lmo + +Now you can compile and upload translation: + + ./po2lmo ../../../applications/luci-app-acl/po/ar/acl.po ./acl.ar.lmo + scp ./acl.ar.lmo root@192.168.1.1:/usr/lib/lua/luci/i18n/ + +You can change language in [System /Language and Style](http://192.168.1.1/cgi-bin/luci/admin/system/system) and check the translation. \ No newline at end of file diff --git a/luci.mk b/luci.mk index dc9f937eff..f398988195 100644 --- a/luci.mk +++ b/luci.mk @@ -18,7 +18,7 @@ LUCI_MINIFY_LUA?=1 LUCI_MINIFY_CSS?=1 LUCI_MINIFY_JS?=1 -# Language code titles +#LUCI_LANG_START LUCI_LANG.ar=العربية (Arabic) LUCI_LANG.bg=български (Bulgarian) LUCI_LANG.bn_BD=বাংলা (Bengali) @@ -42,8 +42,8 @@ LUCI_LANG.ms=Bahasa Melayu (Malay) LUCI_LANG.nb_NO=Norsk (Norwegian) LUCI_LANG.nl=Nederlands (Dutch) LUCI_LANG.pl=Polski (Polish) -LUCI_LANG.pt_BR=Português do Brasil (Brazilian Portuguese) LUCI_LANG.pt=Português (Portuguese) +LUCI_LANG.pt_BR=Português do Brasil (Brazilian Portuguese) LUCI_LANG.ro=Română (Romanian) LUCI_LANG.ru=Русский (Russian) LUCI_LANG.sk=Slovenčina (Slovak) @@ -53,6 +53,7 @@ LUCI_LANG.uk=Українська (Ukrainian) LUCI_LANG.vi=Tiếng Việt (Vietnamese) LUCI_LANG.zh_Hans=简体中文 (Chinese Simplified) LUCI_LANG.zh_Hant=繁體中文 (Chinese Traditional) +#LUCI_LANG_END # Submenu titles LUCI_MENU.col=1. Collections @@ -129,10 +130,10 @@ include $(INCLUDE_DIR)/package.mk # LUCI_SUBMENU: the submenu-item below the LuCI top-level menu inside OpenWrt menuconfig # usually one of the LUCI_MENU.* definitions -# LUCI_SUBMENU_DEFAULT: the regular SUBMENU defined by LUCI_TYPE or derrived from the packagename -# LUCI_SUBMENU_FORCED: manually forced value SUBMENU to set to by explicit definiton +# LUCI_SUBMENU_DEFAULT: the regular SUBMENU defined by LUCI_TYPE or derived from the packagename +# LUCI_SUBMENU_FORCED: manually forced value SUBMENU to set to by explicit definition # can be any string, "none" disables the creation of a submenu -# most usefull in combination with LUCI_CATEGORY, to make the package appear +# most useful in combination with LUCI_CATEGORY, to make the package appear # anywhere in the menu structure LUCI_SUBMENU_DEFAULT=$(if $(LUCI_MENU.$(LUCI_TYPE)),$(LUCI_MENU.$(LUCI_TYPE)),$(LUCI_MENU.app)) LUCI_SUBMENU=$(if $(LUCI_SUBMENU_FORCED),$(LUCI_SUBMENU_FORCED),$(LUCI_SUBMENU_DEFAULT)) -- 2.30.2