mirror of
https://github.com/imputnet/helium.git
synced 2026-10-19 01:39:56 -05:00
devutils/i18n: add forage command (#1944)
forages for strings that were already translated in the past within chromium and adds them to the translations, effectively reducing effort for review
This commit is contained in:
2 files changed
+122
No files matched your search
@@ -55,6 +55,13 @@ def parse_args():
|
||||
|
||||
subparsers.add_parser('clean', help='Clean up stale translation strings')
|
||||
|
||||
forage = subparsers.add_parser('forage', help='Find existing translation strings in Chromium')
|
||||
forage.add_argument('-t',
|
||||
'--tree',
|
||||
type=Path,
|
||||
required=True,
|
||||
help='Path to Chromium source tree')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -74,6 +81,10 @@ def main():
|
||||
import i18n_clean # pylint: disable=import-outside-toplevel
|
||||
return i18n_clean.run()
|
||||
|
||||
if args.command == 'forage':
|
||||
import i18n_forage # pylint: disable=import-outside-toplevel
|
||||
return i18n_forage.run(args)
|
||||
|
||||
raise ValueError(f'unknown command: {args.command}')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# Copyright 2026 The Helium Authors
|
||||
# You can use, redistribute, and/or modify this source code under
|
||||
# the terms of the GPL-3.0 license that can be found in the LICENSE file.
|
||||
"""Helper for finding existing translated strings in upstream Chromium."""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
import xml.etree.ElementTree as xml
|
||||
|
||||
I18N_DIR = Path(__file__).resolve().parent.parent / 'i18n'
|
||||
SOURCE_PATH = I18N_DIR / 'source.gen.json'
|
||||
TRANSLATIONS_DIR = I18N_DIR / 'translations'
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'utils'))
|
||||
from i18n_apply import get_id, build_xtb_index, resolve_xtb
|
||||
import name_substitution_utils as namesub
|
||||
|
||||
|
||||
def get_untranslated_strings_by_xtb(source, lang, xtb_index):
|
||||
"""Generates a map of xtb_path -> [untranslated strings]."""
|
||||
translation_path = TRANSLATIONS_DIR / f'{lang}.json'
|
||||
trans_keys = set()
|
||||
|
||||
if translation_path.exists():
|
||||
with open(translation_path, encoding='utf-8') as file:
|
||||
translations = json.load(file)
|
||||
for translation in translations:
|
||||
trans_keys.add((translation['name'], translation['source']))
|
||||
|
||||
filtered = defaultdict(list)
|
||||
for string in source:
|
||||
xtb_file = resolve_xtb(string, lang, xtb_index)
|
||||
assert xtb_file is not None
|
||||
if (string['name'], string['message']) not in trans_keys:
|
||||
filtered[xtb_file].append(string)
|
||||
return filtered
|
||||
|
||||
|
||||
def to_fpmap(strings):
|
||||
"""Generates a fingerprint -> source string mapping."""
|
||||
fp_map = {}
|
||||
for string in strings:
|
||||
fingerprint = get_id(string['name'], string['context'], string['message'],
|
||||
string.get('meaning'))
|
||||
fp_map[fingerprint] = string
|
||||
return fp_map
|
||||
|
||||
|
||||
def stringify_inner_xml(node):
|
||||
"""Takes an XML node and stringifies its inner contents."""
|
||||
parts = [node.text or '']
|
||||
for child in node:
|
||||
parts.append(xml.tostring(child, encoding='unicode'))
|
||||
return ''.join(parts)
|
||||
|
||||
|
||||
def find_matching(path, fpmap):
|
||||
"""Finds pre-existing matching translations for Helium strings."""
|
||||
content = path.read_text(encoding='utf-8')
|
||||
root = xml.fromstring(content)
|
||||
|
||||
out = {}
|
||||
for node in root.findall('.//translation'):
|
||||
fingerprint = node.get('id')
|
||||
if fingerprint in fpmap:
|
||||
out[fingerprint] = stringify_inner_xml(node)
|
||||
return out
|
||||
|
||||
|
||||
def apply_matching(lang, matching, fpmap):
|
||||
"""
|
||||
Takes a map of found matches and applies them to the helium i18n file.
|
||||
"""
|
||||
translation_path = TRANSLATIONS_DIR / f'{lang}.json'
|
||||
translations = []
|
||||
if translation_path.exists():
|
||||
with open(translation_path, encoding='utf-8') as file:
|
||||
translations = json.load(file)
|
||||
|
||||
for fingerprint, translation in matching.items():
|
||||
original = fpmap[fingerprint]
|
||||
translations.append({
|
||||
'name': namesub.replace_text(original['name'])[0],
|
||||
'source': namesub.replace_text(original['message'])[0],
|
||||
'message': namesub.replace_text(translation)[0],
|
||||
})
|
||||
|
||||
with open(translation_path, 'w', encoding='utf-8') as file:
|
||||
json.dump(translations, file, indent=2, ensure_ascii=False)
|
||||
file.write('\n')
|
||||
|
||||
|
||||
def run(args):
|
||||
"""Forages for usable existing Chromium translations."""
|
||||
with open(SOURCE_PATH, encoding='utf-8') as file:
|
||||
source = json.load(file)
|
||||
with open(I18N_DIR / 'languages.json', encoding='utf-8') as file:
|
||||
languages = json.load(file)
|
||||
|
||||
namesub.add_grit_to_path(args.tree)
|
||||
xtb_map = build_xtb_index(source, args.tree)
|
||||
|
||||
for lang in languages:
|
||||
missing = get_untranslated_strings_by_xtb(source, lang, xtb_map)
|
||||
for path, strings in missing.items():
|
||||
fpmap = to_fpmap(strings)
|
||||
matching = find_matching(path, fpmap)
|
||||
if matching:
|
||||
apply_matching(lang, matching, fpmap)
|
||||
Reference in new issue
Block a user