diff options
| -rw-r--r-- | ios/pylintrc | 14 | ||||
| -rw-r--r-- | ios/requirements.txt | 42 | ||||
| -rw-r--r-- | ios/update_translations.py | 194 |
3 files changed, 250 insertions, 0 deletions
diff --git a/ios/pylintrc b/ios/pylintrc new file mode 100644 index 0000000000..35ec9d3f74 --- /dev/null +++ b/ios/pylintrc @@ -0,0 +1,14 @@ +[FORMAT] +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=2 + +[MESSAGES CONTROL] + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). diff --git a/ios/requirements.txt b/ios/requirements.txt new file mode 100644 index 0000000000..a2a01cd1d5 --- /dev/null +++ b/ios/requirements.txt @@ -0,0 +1,42 @@ +nslocalized==0.2.0 \ + --hash=sha256:46d6f776b26e9da0e0d0c759d99782884882cade59d5c935005a85cba654db9e +## The following requirements were added by pip freeze: +astroid==2.6.2 +cffi==1.14.5 \ + --hash=sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69 \ + --hash=sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06 \ + --hash=sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05 \ + --hash=sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73 \ + --hash=sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49 \ + --hash=sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4 \ + --hash=sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62 \ + --hash=sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc \ + --hash=sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1 \ + --hash=sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c +chardet==4.0.0 \ + --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \ + --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 +click==8.0.1 \ + --hash=sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a \ + --hash=sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6 +GDAL==3.3.1 \ + --hash=sha256:776ad8f499919d6824b7ba3ae8bd53b2df5d73a14dc047ebc8f33f9507c8dcc5 +hashin==0.15.0 +igenstrings==1.1.0 +isort==5.9.1 +lazy-object-proxy==1.6.0 +mccabe==0.6.1 +packaging==21.0 +pip-api==0.0.20 +pycparser==2.20 +pylint==2.9.3 +pyparsing==2.4.7 +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 +tinycss2==1.1.0 \ + --hash=sha256:0353b5234bcaee7b1ac7ca3dea7e02cd338a9f8dcbb8f2dcd32a5795ec1e5f9a \ + --hash=sha256:fbdcac3044d60eb85fdb2aa840ece43cf7dbe798e373e6ee0be545d4d134e18a +toml==0.10.2 +webencodings==0.5.1 +wrapt==1.12.1
\ No newline at end of file diff --git a/ios/update_translations.py b/ios/update_translations.py new file mode 100644 index 0000000000..51a9f10068 --- /dev/null +++ b/ios/update_translations.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 + +""" +A helper script to parse NSLocalizedString in Swift source files and extract and merge new +translations with the existing ones. +""" + +import os +from os import path +from subprocess import Popen, PIPE +import shutil +from nslocalized import StringTable + +# Current script dir +SCRIPT_DIR = path.dirname(path.realpath(__file__)) + +# Path to directory with source files (Swift) +SOURCE_PATH = path.join(SCRIPT_DIR, "MullvadVPN") + +# Path to directory with base localizations +BASE_LANGUAGE_PATH = path.join(SOURCE_PATH, "en.lproj") + +# Path to directory with the output of genstrings tool. +GENSTRINGS_OUTPUT_PATH = path.join(SCRIPT_DIR, "genstrings-out") + +# Output encoding for strings files. +# +# By default genstrings tool outputs text in utf-16, which git recognizes as binary, therefore +# rendering diff capability useless. +# +# Store localization files in utf-8 to fix this. Xcode automatically transcodes localization +# files to utf-16 during the build phase. +OUTPUT_ENCOODING = "utf_8" + + +def check_file_extension(file, expected_extension): + """ + Returns True if the file extension matches the expected one. + """ + (_basename, ext) = os.path.splitext(file) + return ext == expected_extension + + +def get_source_files(): + """ + Find all Swift source files recursively. + """ + results = [] + for root, _dirs, files in os.walk(SOURCE_PATH): + for file in files: + if check_file_extension(file, ".swift"): + results.append(path.join(root, file)) + return results + + +def get_strings_files(dir_path): + """ + Find all .strings files within the given directory. + """ + results = [] + for file in os.listdir(dir_path): + if check_file_extension(file, ".strings"): + results.append(file) + return results + + +def create_empty_output_dir(): + """ + Creates empty directory for output of genstrings tool. + """ + # Wipe out old files + delete_output_dir() + + # Re-create out directory + os.mkdir(GENSTRINGS_OUTPUT_PATH) + + +def delete_output_dir(): + """ + Delete directory used for genstrings output + """ + if path.exists(GENSTRINGS_OUTPUT_PATH): + shutil.rmtree(GENSTRINGS_OUTPUT_PATH) + + +def extract_translations(): + """ + Extract translations from sources using genstrings tool. + """ + + # Get Swift source files + source_files = get_source_files() + + # Genstrings utility comes with Xcode and used for extracting the localizable strings from source + # files and producing string tables. + args = ( + "genstrings", "-o", GENSTRINGS_OUTPUT_PATH, + *source_files + ) + (exit_code, errors) = run_program(*args) + + if exit_code == 0: + print("Genstrings finished without errors.") + else: + print("Genstrings exited with {}: {}".format(exit_code, errors.decode().strip())) + + +def merge_translations(): + """ + Merge string tables, delete stale ones and copy new ones. + """ + + # Existing string tables + existing_string_tables = get_strings_files(BASE_LANGUAGE_PATH) + + # Newly generated string tables + new_string_tables = get_strings_files(GENSTRINGS_OUTPUT_PATH) + + # String tables that will be merged + merge_string_tables = [] + + # Detect new string tables + for table_name in new_string_tables: + if table_name not in existing_string_tables: + src = path.join(GENSTRINGS_OUTPUT_PATH, table_name) + dst = path.join(BASE_LANGUAGE_PATH, table_name) + + print("Copying {} to {}".format(src, dst)) + new_table = StringTable.read(src) + new_table.write(dst, encoding=OUTPUT_ENCOODING) + + # Detect removed string tables + for table_name in existing_string_tables: + if table_name in new_string_tables: + merge_string_tables.append(table_name) + else: + filepath = path.join(BASE_LANGUAGE_PATH, table_name) + + print("Removing {}".format(filepath)) + os.unlink(filepath) + + # Merge remaining string tables + for table_name in merge_string_tables: + new_table_path = path.join(GENSTRINGS_OUTPUT_PATH, table_name) + base_table_path = path.join(BASE_LANGUAGE_PATH, table_name) + + merge_two_tables(base_table_path, new_table_path) + + +def merge_two_tables(base_table_path, new_table_path): + """ + Merge new table into base table. + """ + # Existing string table previously generated from sources + base_table = StringTable.read(base_table_path) + + # New string table generated from sources + new_table = StringTable.read(new_table_path) + + print("Merging {} into {}".format(new_table_path, base_table_path)) + + # Iterate through newly generated table and preserve existing translations. + for new_key in new_table.strings: + base_entry = base_table.lookup(new_key) + new_entry = new_table.lookup(new_key) + if base_entry is not None: + new_entry.target = base_entry.target + + print("Write {} on disk.".format(base_table_path)) + new_table.write(base_table_path, encoding=OUTPUT_ENCOODING) + + +def run_program(*args): + """ + Run program and return a tuple with returncode and errors. + """ + with Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) as subproc: + print("Run: {}".format(' '.join(args))) + + errors = subproc.communicate()[1] + return (subproc.returncode, errors) + + +def main(): + """ + Program entry + """ + create_empty_output_dir() + extract_translations() + merge_translations() + delete_output_dir() + + +main() |
