Skip to content

Commit

Permalink
WIP: Adds new lang 'fr' for French
Browse files Browse the repository at this point in the history
Adds new Internationalization Helper tool: i18n_helper.py

Signed-off-by: Tom Marble <[email protected]>
  • Loading branch information
tmarble committed Oct 27, 2022
1 parent e55c749 commit 07cb239
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 2 deletions.
64 changes: 64 additions & 0 deletions locales/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,67 @@ needs to have some prompts for the user in order to unlock the filesystem in the
Nothing prevents a more sophisticated application-level server later on that operates
in `std` from pulling in a more featureful, dynamic localization framework; but it's an
explicit goal to keep the kernel small, simple, and fast.

## Internationalization Helper

In the tools directory you find `i18n_helper.py` that can be
very useful for managing and adding localizations:

```
$ ./tools/i18n_helper.py -h
usage: i18n_helper.py [-h] [-v] [-l] [-i] [-m] [-o] [-n NEW_LANG] [-f FROM_LANG]
Xous i18n Helper
options:
-h, --help show this help message and exit
-v, --verbose Prints details of each action
-l, --list-languages Lists current translations
-i, --list-i18n-files
Lists i18n files
-m, --missing Shows missing translations
-o, --show-ok Shows OK translations
-n NEW_LANG, --new-lang NEW_LANG
Add support for a new lang
-f FROM_LANG, --from-lang FROM_LANG
Copy this existing lang for the new lang
$
```

The `--list-languages` function will show the currently supported langs:

```
$ ./tools/i18n_helper.py --list-languages
en
ja
zh
en-tts
$
```

The `--list-languages` function will show the disposition of the translations
(add `--show-ok` to see the complete translation status). The output
is "FILE tab JQ-LIKE-PATH tab STATUS" which makes it easy to spot
situations like completely absent translations that might cause a panic:

```
$ ./tools/i18n_helper.py --missing | grep ABSENT
services/status/locales/i18n.json rtc.set_time_modal.ja ABSENT
services/status/locales/i18n.json rtc.set_time_modal.zh ABSENT
services/status/locales/i18n.json rtc.set_time_modal.en-tts ABSENT
$
```

The `--new-lang` function will add a new lang by copying an
existing lang and adding the suffix `" *EN*"` to the new translation
(example if the `--from-lang` is `en`):


```
$ ./tools/i18n_helper.py --verbose --from-lang en --new-lang fr
verbose mode
-- get languages --
adding new lang "fr" from "en" by appending " *EN*"
NOT IMPLEMENTED YET
$
```
7 changes: 5 additions & 2 deletions services/status/locales/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,10 @@
"en-tts": "NTP query failed, please enter time manually."
},
"rtc.set_time_modal": {
"en": "Enter time"
"en": "Enter time",
"ja": "Enter time *EN*",
"zh": "Enter time *EN*",
"en-tts": "Enter time *EN*"
},
"rtc.month": {
"en": "Month (1-12)",
Expand Down Expand Up @@ -525,4 +528,4 @@
"zh": "错误:输入超出范围",
"en-tts": "Error: input out of range"
}
}
}
4 changes: 4 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Found Xous Args Size at offset 8, setting total_words to 208
$
```

## Internationalization Helper

For more about `i18n_helper.py` please see the locales [README](../locales/README.md)

## Testing

_TBD_
Expand Down
197 changes: 197 additions & 0 deletions tools/i18n_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#! /usr/bin/env python3

import argparse
import json
import sys
import os
import os.path
import pwd
import re

class Main(object):

def __init__(self):
parser = argparse.ArgumentParser(description="Xous i18n Helper")
parser.add_argument('-v', '--verbose',
help='Prints details of each action',
action='store_true', required=False)
parser.add_argument('-l', '--list-languages',
help='Lists current translations',
action='store_true', required=False)
parser.add_argument('-i', '--list-i18n-files',
help='Lists i18n files',
action='store_true', required=False)
parser.add_argument('-m', '--missing',
help='Shows missing translations',
action='store_true', required=False)
parser.add_argument('-o', '--show-ok',
help='Shows OK translations',
action='store_true', required=False)
parser.add_argument('-n', '--new-lang',
help='Add support for a new lang',
required=False)
parser.add_argument('-f', '--from-lang',
help='Copy this existing lang for the new lang',
required=False)
self.args = parser.parse_args()
self.args.program = sys.argv[0]
if self.args.program[0] != '/':
self.args.program = os.path.join(os.getcwd(), self.args.program)
self.args.pdir = os.path.normpath(os.path.dirname(self.args.program))
self.args.xousdir = os.path.normpath(os.path.dirname(self.args.pdir))
self.args.program = os.path.basename(self.args.program)
self.errfile = sys.stderr

def out(self, *objects):
print(*objects)

def err(self, *objects):
print(*objects, file=self.errfile)

def verr(self, *objects):
if self.args.verbose:
self.err(*objects)

def get_languages(self):
self.verr('-- get languages --')
# services/root-keys/locales/i18n.json
root_keys = os.path.join(self.args.xousdir, 'services', 'root-keys', 'locales', 'i18n.json')
if not os.path.exists(root_keys):
self.err('cannot find: %s' % root_keys);
return 1
# read keys in "rootkeys.backup_key":
essential = 'rootkeys.backup_key'
f = open(root_keys,)
obj = json.load(f)
self.languages = []
if essential in obj:
for key in obj[essential].keys():
self.languages.append(key)
f.close()
return 0

def list_languages(self):
if self.get_languages() != 0:
return 1
self.verr('-- list languages --')
for lang in self.languages:
self.out(lang)
return 0

def get_i18n_files(self):
self.verr('-- get i18n files --')
self.i18n_files = []
topdirs = ['apps', 'services']
for top in topdirs:
topdir = os.path.join(self.args.xousdir, top)
if os.path.exists(topdir):
if top == 'apps':
manifest = os.path.join(topdir, 'manifest.json')
if os.path.exists(manifest):
self.i18n_files.append(manifest)
for thing in os.listdir(topdir):
thingdir = os.path.join(topdir, thing)
if os.path.isdir(thingdir):
i18n_path = os.path.join(thingdir, 'locales', 'i18n.json')
if os.path.exists(i18n_path):
self.i18n_files.append(i18n_path[len(self.args.xousdir)+1:])
return 0

def list_i18n_files(self):
if self.get_i18n_files() != 0:
return 1
self.verr('-- list i18n files --')
for pathname in self.i18n_files:
self.out(pathname)
return 0

# keys for
# manifest APP, menu_name, appmenu.APP
# other TAG
def show_missing(self, i18n, is_manifest):
i18n_path = os.path.join(self.args.xousdir, i18n)
f = open(i18n_path,)
obj = json.load(f)
f.close()
jqpath = None
translation = None
for tag in obj.keys():
if is_manifest:
appmenu = 'appmenu.' + tag
jqpath = tag + '.menu_name.' + appmenu
translation = obj[tag]['menu_name'][appmenu]
else:
jqpath = tag
translation = obj[tag]
for lang in self.languages:
lang_path = jqpath + '.' + lang
status = 'OK'
if lang in translation:
t = translation[lang]
# to print translation
# print('%s\t%s\t%s' % (i18n, lang_path, t))
if t == '🔇':
status = 'MISSING'
elif self.regex.fullmatch(t):
status = 'TEMPORARY'
else:
status = 'ABSENT'
if self.args.show_ok or status != 'OK':
print('%s\t%s\t%s' % (i18n, lang_path, status))
return 0

def missing(self):
if self.get_languages() != 0:
return 1
if self.get_i18n_files() != 0:
return 1
self.verr('-- missing --')
# if end of string matches ' *EN*' (or some other lang)
self.regex = re.compile('^.* \*[a-zA-Z]{2}\*$')
for i18n in self.i18n_files:
is_manifest = os.path.basename(i18n) == 'manifest.json'
if self.show_missing(i18n, is_manifest) != 0:
return 1
return 0

def new_lang(self):
self.from_hint = ' *%s*' % self.args.from_lang.upper()
self.verr('adding new lang "%s" from "%s" by appending "%s"' % (self.args.new_lang, self.args.from_lang, self.from_hint))
self.err('NOT IMPLEMENTED YET')
return 1

def run(self):
rc = 0
if self.args.verbose:
self.err('verbose mode')
self.err('xous dir: "%s"' % self.args.xousdir)
self.err('program dir: "%s"' % self.args.pdir)
self.err('program: "%s"' % self.args.program)
self.err('show-ok: "%s"' % self.args.show_ok)
if self.args.list_languages:
rc = self.list_languages()
elif self.args.list_i18n_files:
rc = self.list_i18n_files()
elif self.args.missing:
rc = self.missing()
elif self.args.new_lang:
if not self.args.from_lang:
self.err('error: you must specify both --from-lang en --to-lang fr')
rc = 1
else:
self.get_languages()
if not self.args.from_lang in self.languages:
self.err('error: --from-lang %s is not one of the existing langs: %s' % (self.args.from_lang, self.languages))
rc = 1
elif self.args.new_lang in self.languages:
self.err('error: --new-lang %s must not be one of the existing langs: %s' % (self.args.from_lang, self.languages))
rc = 1
elif not re.fullmatch('^[a-z-]{2,6}$', self.args.new_lang):
self.err('error: --new-lang %s is not valid lang' % self.args.new_lang)
rc = 1
else:
rc = self.new_lang()
return rc

if __name__ == "__main__":
sys.exit(Main().run())

0 comments on commit 07cb239

Please sign in to comment.