Created
September 23, 2022 07:49
-
-
Save flodolo/3bfa3cc4adb75ac94336007f95371dba to your computer and use it in GitHub Desktop.
Extract and inject iOS translations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
# This Source Code Form is subject to the terms of the Mozilla Public | |
# License, v. 2.0. If a copy of the MPL was not distributed with this | |
# file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
""" | |
Extract translations for a specific string ID in XLIFF file, and store them in | |
a local JSON file. | |
""" | |
from glob import glob | |
from lxml import etree | |
import argparse | |
import os | |
import json | |
import sys | |
NS = {"x": "urn:oasis:names:tc:xliff:document:1.2"} | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"--reference", | |
required=True, | |
dest="reference_locale", | |
help="Reference locale code", | |
) | |
parser.add_argument( | |
"--path", | |
required=True, | |
dest="base_folder", | |
help="Path to folder including subfolders for all locales", | |
) | |
parser.add_argument( | |
"--id", | |
required=True, | |
dest="string_id", | |
help="String ID to extract", | |
) | |
args = parser.parse_args() | |
reference_locale = args.reference_locale | |
string_id = args.string_id | |
# Get a list of files to update (absolute paths) | |
base_folder = os.path.realpath(args.base_folder) | |
reference_path = os.path.join(base_folder, reference_locale) | |
# Get a list of all the reference XLIFF files | |
reference_files = [] | |
for xliff_path in glob(reference_path + "/**/*.xliff", recursive=True): | |
reference_files.append(os.path.relpath(xliff_path, reference_path)) | |
if not reference_files: | |
sys.exit( | |
f"No reference file found in {os.path.join(base_folder, reference_locale)}" | |
) | |
# Get the list of locales, including reference | |
locales = [ | |
d | |
for d in os.listdir(base_folder) | |
if os.path.isdir(os.path.join(base_folder, d)) and not d.startswith(".") | |
] | |
locales.sort() | |
translations = {} | |
for filename in reference_files: | |
for locale in locales: | |
l10n_file = os.path.join(base_folder, locale, filename) | |
if not os.path.isfile(l10n_file): | |
continue | |
# Read XML file | |
try: | |
locale_tree = etree.parse(l10n_file) | |
locale_root = locale_tree.getroot() | |
except Exception as e: | |
print(f"ERROR: Can't parse {l10n_file}") | |
print(e) | |
continue | |
for trans_node in locale_root.xpath("//x:trans-unit", namespaces=NS): | |
for child in trans_node.xpath("./x:target", namespaces=NS): | |
original_id = trans_node.get("id") | |
if original_id == args.string_id: | |
translations[locale] = child.text | |
# Save translations in a JSON file | |
with open("translations.json", "w") as fp: | |
json.dump(translations, fp, indent=2, sort_keys=True, ensure_ascii=False) | |
if not translations: | |
print(f"No translations found for ID: {string_id}") | |
if __name__ == "__main__": | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
# This Source Code Form is subject to the terms of the Mozilla Public | |
# License, v. 2.0. If a copy of the MPL was not distributed with this | |
# file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
""" | |
Inject translations into localized XLIFF files | |
""" | |
from glob import glob | |
from lxml import etree | |
from translate.misc.xml_helpers import reindent | |
import argparse | |
import json | |
import os | |
import sys | |
NS = {"x": "urn:oasis:names:tc:xliff:document:1.2"} | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"--reference", | |
required=True, | |
dest="reference_locale", | |
help="Reference locale code", | |
) | |
parser.add_argument( | |
"--path", | |
required=True, | |
dest="base_folder", | |
help="Path to folder including subfolders for all locales", | |
) | |
parser.add_argument( | |
"--id", | |
required=True, | |
dest="string_id", | |
help="String ID to create", | |
) | |
parser.add_argument( | |
"--file", | |
required=True, | |
dest="file_id", | |
help="File element where to inject the ID", | |
) | |
parser.add_argument("locales", nargs="*", help="Locales to process") | |
args = parser.parse_args() | |
reference_locale = args.reference_locale | |
string_id = args.string_id | |
# Get a list of files to update (absolute paths) | |
base_folder = os.path.realpath(args.base_folder) | |
reference_path = os.path.join(base_folder, reference_locale) | |
# Get a list of all the reference XLIFF files | |
reference_files = [] | |
for xliff_path in glob(reference_path + "/**/*.xliff", recursive=True): | |
reference_files.append(os.path.relpath(xliff_path, reference_path)) | |
if not reference_files: | |
sys.exit( | |
f"No reference file found in {os.path.join(base_folder, reference_locale)}" | |
) | |
# Get the list of locales, including reference | |
locales = [ | |
d | |
for d in os.listdir(base_folder) | |
if os.path.isdir(os.path.join(base_folder, d)) and not d.startswith(".") | |
] | |
locales.sort() | |
# Read translations.json file | |
with open("translations.json", "r") as fp: | |
translations = json.load(fp) | |
for filename in reference_files: | |
for locale in locales: | |
l10n_file = os.path.join(base_folder, locale, filename) | |
if not os.path.isfile(l10n_file): | |
continue | |
print(f"Updating {l10n_file}") | |
# Read localized XML file | |
try: | |
locale_tree = etree.parse(l10n_file) | |
locale_root = locale_tree.getroot() | |
except Exception as e: | |
print(f"ERROR: Can't parse {l10n_file}") | |
print(e) | |
continue | |
for body_node in locale_root.xpath("//x:file/x:body", namespaces=NS): | |
# Continue if it's not the requested file ID | |
if body_node.getparent().get("original") != args.file_id: | |
continue | |
# Check if the ID already exists in the file | |
existing_node = locale_root.xpath( | |
f"//x:file/x:body/x:trans-unit[@id='{string_id}']", namespaces=NS | |
) | |
if existing_node: | |
print(f"The string {string_id} already exists in the file.") | |
continue | |
# Create translation unit | |
# <trans-unit id="STRING ID" xml:space="preserve"> | |
# <source>REFERENCE TEXT</source> | |
# <target>TRANSLATED TEXT</target> | |
# <note/> | |
# </trans-unit> | |
trans_unit = etree.Element("trans-unit") | |
trans_unit.set("id", string_id) | |
trans_unit.set( | |
"{http://www.w3.org/XML/1998/namespace}space", "preserve" | |
) | |
source = etree.SubElement(trans_unit, "source") | |
source.text = translations[reference_locale] | |
target = etree.SubElement(trans_unit, "target") | |
target.text = translations.get(locale, "") | |
etree.SubElement(trans_unit, "note") | |
body_node.append(trans_unit) | |
# Replace the existing locale file with the new XML content | |
with open(l10n_file, "w") as fp: | |
# Fix identation of XML file | |
reindent(locale_root) | |
xliff_content = etree.tostring( | |
locale_root, | |
encoding="UTF-8", | |
xml_declaration=True, | |
pretty_print=True, | |
) | |
fp.write(xliff_content.decode("utf-8")) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment