summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/pylintrc14
-rw-r--r--ios/requirements.txt42
-rw-r--r--ios/update_translations.py194
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()