| #!/usr/bin/env python3 |
| # Copyright 2019 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Import generated translations from Google's internal Translation Console.""" |
| |
| import glob |
| import json |
| import logging |
| import os |
| import sys |
| |
| import nassh # pylint: disable=wrong-import-order |
| import libdot |
| |
| import filter_translations # pylint: disable=wrong-import-order |
| |
| |
| def get_parser(): |
| """Get a command line parser.""" |
| parser = libdot.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| "--skip-git", |
| dest="run_git", |
| action="store_false", |
| default=True, |
| help="Skip creating a new git commit.", |
| ) |
| parser.add_argument( |
| "builddir", help="Input path to the compiled locale messages." |
| ) |
| parser.add_argument( |
| "sourcedir", |
| nargs="?", |
| default=os.path.join(nassh.DIR, "_locales"), |
| help="Output path to nassh/_locales/ directory.", |
| ) |
| return parser |
| |
| |
| def main(argv): |
| """The main func!""" |
| parser = get_parser() |
| opts = parser.parse_args(argv) |
| |
| opts.builddir = os.path.abspath(opts.builddir) |
| opts.sourcedir = os.path.abspath(opts.sourcedir) |
| |
| # The english translation is the hand maintained baseline. |
| with open(os.path.join(opts.sourcedir, "en", "messages.json"), "rb") as fp: |
| baseline = json.load(fp) |
| # The translation process normalizes all the ids to lowercase, so do that |
| # here too to simplify checks later on. |
| for msgid, msg in sorted(baseline.items()): |
| libdot.minify_translations.minify_placeholders(msg) |
| baseline[msgid.lower()] = baseline.pop(msgid) |
| |
| # Find new translations. Do it here for smoke checking. |
| new_locales = glob.glob(os.path.join(opts.builddir, "*", "messages.json")) |
| if not new_locales: |
| parser.error("builddir doesn't seem to contain any translations") |
| |
| # Load & clear existing translations. |
| logging.info("Clearing existing translation files") |
| old_trans = {} |
| os.makedirs(opts.sourcedir, exist_ok=True) |
| for locale in os.listdir(opts.sourcedir): |
| locale_dir = os.path.join(opts.sourcedir, locale) |
| path = os.path.join(locale_dir, "messages.json") |
| try: |
| with open(path, "rb") as fp: |
| old_trans[locale] = json.load(fp) |
| except FileNotFoundError: |
| # Completely new translations won't exist yet. |
| pass |
| |
| # Special case: English messages are our source of truth, so we'd never |
| # import those from the dump. Do not try to delete the file. |
| if locale == "en": |
| continue |
| |
| libdot.unlink(path) |
| |
| # Prune empty dirs. |
| try: |
| os.rmdir(locale_dir) |
| except OSError: |
| pass |
| |
| def normalize(msg): |
| """Hack up the message string. |
| |
| English translations don't roundtrip. Sometimes the whitespae gets |
| mangled -- leading, trailing, or inline. Try to reduce it all down |
| so we can compare it to the original English input. |
| """ |
| return msg["message"].replace(" ", "") |
| |
| # Copy over the new translations, and merge it with existing content. |
| logging.info("Importing new translation files") |
| for in_locale in new_locales: |
| locale = os.path.basename(os.path.dirname(in_locale)) |
| out_locale = os.path.join(opts.sourcedir, locale, "messages.json") |
| os.makedirs(os.path.dirname(out_locale), exist_ok=True) |
| |
| # Strip out untranslated strings. This saves space, and we'd rather |
| # use the older (perhaps stale) translations than the english one. |
| with open(in_locale, "rb") as fp: |
| new = json.load(fp) |
| |
| # Strip translations only used by the CWS. |
| new.pop("cws_description", None) |
| new.pop("cws_description_dev", None) |
| |
| old = old_trans.get(locale, {}) |
| for msgid, msg in sorted(new.items()): |
| # The conversion process is a bit buggy :(. |
| msg["message"] = msg["message"].replace( |
| "chromium- hterm@chromium.org", "chromium-hterm@chromium.org" |
| ) |
| |
| # The CWS requires manifest strings be in all locales. It should |
| # fall back to English automatically, but it doesn't. |
| if msgid == "nassh_product_name": |
| new.setdefault(msgid, baseline[msgid]) |
| continue |
| |
| # If we've deleted a message, delete it from the translations too. |
| if msgid not in baseline: |
| new.pop(msgid) |
| continue |
| |
| if normalize(baseline[msgid]) == normalize(msg): |
| if msgid in old: |
| new[msgid] = old[msgid] |
| else: |
| new.pop(msgid) |
| |
| # If we don't have any translations yet, ignore it. |
| if len(new) <= 1: |
| continue |
| |
| filter_translations.reformat(new, output=out_locale) |
| |
| # Generate git commits automatically. |
| if opts.run_git: |
| libdot.run(["git", "checkout", "-f", "en/"], cwd=opts.sourcedir) |
| libdot.run(["git", "add", "."], cwd=opts.sourcedir) |
| libdot.run( |
| ["git", "commit", "-m", "nassh: update translations", "."], |
| cwd=opts.sourcedir, |
| ) |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv[1:])) |