Files

184 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Genera qml/js/i18n.js e qml/js/catalog.js dai sorgenti Flutter di Henflow.
- i18n.js: le 4 mappe di traduzione (it/en/fr/de, 111 chiavi) estratte
verbatim da lib/app_language.dart + la funzione t(key, arg, lang).
- catalog.js: le 7 razze di lib/models/breed_catalog.dart con il path delle
foto installate nel datadir dell'app.
I file generati sono ES5 (motore QML di Qt 5.6) con guard module.exports.
"""
import json
import os
import re
SRC = "/home/kaneda/workspace/henflow-flutter"
DST = "/home/kaneda/workspace/harbour-henflow/qml/js"
LANGS = ["it", "en", "fr", "de"]
def dart_unescape(s):
out = []
i = 0
while i < len(s):
c = s[i]
if c == "\\" and i + 1 < len(s):
n = s[i + 1]
out.append({"n": "\n", "t": "\t", "r": "\r", "'": "'", '"': '"', "\\": "\\"}.get(n, n))
i += 2
else:
out.append(c)
i += 1
return "".join(out)
def extract_strings():
# NB: la tabella VIVA e' quella inline in main.dart (126 chiavi).
# app_language.dart e' una copia vecchia (111 chiavi, usata solo da
# lib/dialogs/confirm_dialogs.dart) con gli a capo scritti '\\n' invece
# che '\n': estrarla produrrebbe testi con un backslash visibile.
text = open(os.path.join(SRC, "lib/main.dart"), encoding="utf-8").read()
data = {}
for lang in LANGS:
m = re.search(r"AppLanguage\.%s:\s*\{(.*?)\n \}," % lang, text, re.S)
if not m:
raise SystemExit("blocco %s non trovato" % lang)
pairs = re.findall(r"'([a-z0-9_]+)':\s*'((?:[^'\\]|\\.)*)'", m.group(1))
data[lang] = {k: dart_unescape(v) for k, v in pairs}
print("%s: %d chiavi" % (lang, len(data[lang])))
# L'app non ha fallback: t() su chiave assente restituisce la CHIAVE
# (in francese la razza "generic" usciva come "name_generic"). Nel porting
# i buchi si colmano con l'inglese e si segnalano qui.
keys = set(data["en"])
for lang in LANGS:
if lang == "en":
continue
missing = sorted(keys - set(data[lang]))
extra = sorted(set(data[lang]) - keys)
if extra:
raise SystemExit("%s: chiavi non presenti in inglese: %s" % (lang, extra))
for k in missing:
data[lang][k] = data["en"][k]
if missing:
print(" %s: colmate dall'inglese %d chiavi: %s" % (lang, len(missing), missing))
return data, len(keys)
def extract_catalog():
text = open(os.path.join(SRC, "lib/models/breed_catalog.dart"), encoding="utf-8").read()
items = []
for block in re.findall(r"BreedCatalogItem\((.*?)\)\s*,?\s*(?=BreedCatalogItem|\];)", text, re.S):
def field(name):
m = re.search(r"%s:\s*'([^']*)'" % name, block)
return m.group(1) if m else None
def number(name):
m = re.search(r"%s:\s*([0-9.]+)" % name, block)
return float(m.group(1)) if m else None
asset = field("assetPath")
items.append({
"id": field("id"),
"name": field("name"),
"description": field("description"),
"eggColor": field("eggColor"),
"eggsPerHenPerDay": number("eggsPerHenPerDay"),
"asset": "/usr/share/harbour-henflow/" + asset.replace("assets/", ""),
})
return items
def js_value(v, indent=4):
return json.dumps(v, ensure_ascii=False, indent=indent)
def main():
data, nkeys = extract_strings()
os.makedirs(DST, exist_ok=True)
header = """// GENERATO da tools/gen_from_flutter.py — non modificare a mano.
// Traduzioni estratte verbatim da lib/app_language.dart dell'app Flutter
// (%d chiavi x %d lingue). ES5: il motore QML di Qt 5.6 non supporta
// let/const, arrow function, template literal.
var HF_STRINGS = %s;
// t(chiave, argomento, lingua): come il TranslationService dell'app,
// riempie il primo '{}' (o i successivi, con un array). Fallback: inglese,
// poi la chiave stessa.
function t(key, arg, lang) {
var table = HF_STRINGS[lang] || HF_STRINGS.en;
var text = table[key];
if (text === undefined) text = HF_STRINGS.en[key];
if (text === undefined) return key;
if (arg !== undefined && arg !== null) {
if (Object.prototype.toString.call(arg) === "[object Array]") {
for (var i = 0; i < arg.length; i++) {
text = text.replace("{}", String(arg[i]));
}
} else {
text = text.replace("{}", String(arg));
}
}
return text;
}
function availableLanguages() {
var out = [];
for (var k in HF_STRINGS) {
if (HF_STRINGS.hasOwnProperty(k)) out.push(k);
}
return out.sort();
}
// Lingua effettiva: 'system' segue la lingua del telefono (nome tipo "it_IT"),
// altrimenti si usa il codice scelto. Fallback: inglese.
function langFor(setting, systemName) {
if (setting && setting !== "system") {
return HF_STRINGS[setting] ? setting : "en";
}
var code = String(systemName || "").slice(0, 2).toLowerCase();
return HF_STRINGS[code] ? code : "en";
}
if (typeof module !== "undefined" && module.exports) {
module.exports = {
HF_STRINGS: HF_STRINGS,
t: t,
availableLanguages: availableLanguages,
langFor: langFor
};
}
""" % (nkeys, len(LANGS), js_value(data))
open(os.path.join(DST, "i18n.js"), "w", encoding="utf-8").write(header)
catalog = extract_catalog()
cat_js = """// GENERATO da tools/gen_from_flutter.py — non modificare a mano.
// Catalogo razze da lib/models/breed_catalog.dart: nome e descrizione sono
// chiavi di traduzione, le foto stanno nel datadir dell'app.
var HF_BREEDS = %s;
function breedFromId(id) {
for (var i = 0; i < HF_BREEDS.length; i++) {
if (HF_BREEDS[i].id === id) return HF_BREEDS[i];
}
return null;
}
if (typeof module !== "undefined" && module.exports) {
module.exports = { HF_BREEDS: HF_BREEDS, breedFromId: breedFromId };
}
""" % json.dumps(catalog, ensure_ascii=False, indent=4)
open(os.path.join(DST, "catalog.js"), "w", encoding="utf-8").write(cat_js)
print("scritti: i18n.js (%d chiavi x %d lingue), catalog.js (%d razze)"
% (nkeys, len(LANGS), len(catalog)))
for c in catalog:
print(" ", c["id"], "->", c["asset"])
if __name__ == "__main__":
main()