1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
#!/usr/bin/env python3
"""
Imports translations into OpenRCT2's JSON objects given a language and an input file
"""
import argparse
import glob
import json
import os
import re
import sys
from languages import SUPPORTED_LANGUAGES
def dir_path(string):
""" Checks for a valid dir_path """
if os.path.isdir(string):
return string
raise NotADirectoryError(string)
def get_arg_parser():
""" Command line arguments """
parser = argparse.ArgumentParser(description=\
'Imports translations into OpenRCT2\'s JSON objects.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-o', '--objects', default="objects", help='JSON objects directory')
parser.add_argument('-f', '--fallback', default="en-GB",\
help='Fallback language to check against', choices=SUPPORTED_LANGUAGES)
input_group = parser.add_mutually_exclusive_group(required=True)
input_group.add_argument('-i', '--input', help='Translation dump file to import from')
input_group.add_argument('-d', '--dir', type=dir_path,\
help='Directory with translation dump files to import from')
language_group = parser.add_mutually_exclusive_group(required=True)
language_group.add_argument('-l', '--language', choices=SUPPORTED_LANGUAGES,\
help='Language that is being translated, e.g. ja-JP')
language_group.add_argument('-a', '--all-languages', action='store_true')
parser.add_argument('-v', '--verbose', action='store_true', default=False,\
help='Maximize information printed on screen')
return parser
def parse_required_switch_pairs(args):
""" Make sure only valid switch pairs are used """
single_language = args.language and args.input
all_languages = args.all_languages and args.dir
if not single_language and not all_languages:
print(f"Invalid switch pair. Use '-l <lang> -i <file>' or '-a -d <target_dir>")
sys.exit()
class LessVerboseJSONEncoder(json.JSONEncoder):
""" Custom JSON Encoder that reduces output verbosity """
def iterencode(self, o, _one_shot=False):
list_lvl = 0
for string in super(LessVerboseJSONEncoder, self).iterencode(o, _one_shot=_one_shot):
if string.startswith('['):
list_lvl += 1
string = re.sub(r'\n\s*', '', string).strip()
elif list_lvl > 0:
string = re.sub(r'\n\s*', ' ', string).strip()
if string and string[-1] == ',':
string = string[:-1] + self.item_separator
elif string and string[-1] == ':':
string = string[:-1] + self.key_separator
if string.endswith(']'):
list_lvl -= 1
yield string
def update_object_translation(filename, data, file):
""" Update target object translation """
file = open(filename, "w", encoding="utf8")
json.dump(data, file, indent=4, separators=(',', ': '),
ensure_ascii=False, cls=LessVerboseJSONEncoder)
file.write("\n")
file.close()
def translatable(verbose, data):
""" Checks if the file has string content """
if 'strings' in data:
return True
if verbose:
print(f"No strings in {data['id']} -- skipping")
return False
def is_object_translated(verbose, object_id, strings_by_object):
""" Checks if there are any translations for the given file id """
if object_id in strings_by_object:
return True
if verbose:
print(f"No translations for {object_id} in dump file -- skipping")
return False
def is_key_translated(verbose, obj_id, key, object_json):
""" Checks if the given key of an object is translated in input JSON """
if key in object_json:
return True
if verbose:
print(f"No translation for {obj_id} string '{key}' in dump file -- skipping")
return False
def fallback_key_exists(verbose, fallback_language, obj_id, key, object_json):
""" Checks if there was source language to be translated from """
if fallback_language in object_json:
return True
if verbose:
print(f"No {fallback_language} reference for {obj_id} string '{key}'"
f" in dump file -- probably shouldn't exist; skipping")
return False
def translation_existed(target_lang, translations):
""" Checks if the translation for a given key existed """
if target_lang in translations:
return True
return False
def translation_changed(verbose, obj_id, key, translation, target_string):
""" Checks if the translation for a given key changed """
if target_string == translation:
if verbose:
print(f"Translation for {obj_id} string '{key}' has not changed -- skipping")
return False
return True
def update_translation(target_lang, ref_lang, verbose, filename, strings_by_object):
""" Updates an object translation, if applicable """
file = open(filename, encoding="utf8")
data = json.load(file)
file.close()
obj_id = data['id']
if not translatable(verbose, data):
return
if not is_object_translated(verbose, obj_id, strings_by_object):
return
updated = False
for string_key in data['strings']:
if not is_key_translated(verbose, obj_id, string_key, strings_by_object[obj_id]):
continue
if not fallback_key_exists(verbose, ref_lang, string_key, obj_id,
data['strings'][string_key]):
continue
previously_translated = translation_existed(target_lang, data['strings'][string_key])
if previously_translated:
translation = data['strings'][string_key][target_lang]
else:
translation = data['strings'][string_key][ref_lang]
target_string = strings_by_object[obj_id][string_key]
if translation_changed(verbose, obj_id, string_key, translation, target_string):
print(f"{target_lang}: Updating {obj_id} string '{string_key}'")
data['strings'][string_key][target_lang] = strings_by_object[obj_id][string_key]
updated = True
if updated:
update_object_translation(filename, data, file)
def load_translation(language, fallback_language, verbose, input_filename, objects):
""" Load translation from the given file into each object JSON """
in_file = open(input_filename, encoding="utf8")
strings_by_object = json.load(in_file)
in_file.close()
for filename in glob.iglob(objects + '/**/*.json', recursive=True):
update_translation(language, fallback_language, verbose, filename, strings_by_object)
def load_translations():
""" Load translations from the given files into each object JSON """
parser = get_arg_parser()
args = parser.parse_args()
parse_required_switch_pairs(args)
languages_to_extract = SUPPORTED_LANGUAGES if args.all_languages else [args.language]
for lang in languages_to_extract:
read_file_name = f'{args.dir}/{lang}.json' if args.all_languages else args.input
load_translation(lang, args.fallback, args.verbose, read_file_name, args.objects)
if __name__ == "__main__":
load_translations()
|