scummvm/devtools/generate-android-i18n-strings.py
Le Philousophe 3bb8fb3ba1 ANDROID: Various fixes to strings translator
- Make it independent of current work dir
- Fix languages parsing
- Various stylistic fixes
2024-06-01 12:44:11 +02:00

157 lines
5.4 KiB
Python

#!/usr/bin/env python3
# This script generates dists/android/res/values-<qualifier>/strings.xml files
# to add multilanguage support for android strings in res/values/strings.xml file.
# It considers dists/android/res/values/strings.xml file as a base template to generate
# those files. Relevant translated strings.xml file will be automatically used based on
# the android system's language.
#
# Also, this script generates a fake cpp file (dists/android/strings.xml.cpp) with strings
# from dists/android/res/values/strings.xml wrapped inside _() to be picked up by
# gettext for weblate translations
import polib
import os
import re
import xml.etree.ElementTree as ET
BASE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
def generate_fake_cpp():
cpp_text = '''/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* This is an auto generated dummy file used for sticking strings from
* dists/android/res/values/strings.xml into our translation system
*
*/
#include "common/translation.h" // For catching the file during POTFILES reviews\n
'''
with open(os.path.join(BASE_PATH, 'dists/android.strings.xml.cpp'), 'w') as file:
file.write(cpp_text)
tree = ET.parse(os.path.join(BASE_PATH, 'dists/android/res/values/strings.xml'))
root = tree.getroot()
for string in root.findall('string'):
if string.attrib.get('translatable') != 'false':
file.write(
f'static Common::U32String {string.attrib.get("name")} = _("{string.text}");\n')
def extract_translations(file):
po_file = polib.pofile(os.path.join(BASE_PATH, 'po', file + '.po'))
translations = {}
for entry in po_file:
if entry.msgid and entry.msgstr:
translations[entry.msgid] = entry.msgstr
return translations
def escape_special_characters(translated_string):
'''Some characters like single quote (') have special usage in XML and hence must be escaped.\n
See: https://developer.android.com/guide/topics/resources/string-resource.html#escaping_quotes
'''
escaped = translated_string.replace('@', '\\@')
escaped = escaped.replace('?', '\\?')
escaped = escaped.replace('\'', '\\\'')
escaped = escaped.replace('\"', '\\\"')
escaped = escaped.replace('\n', '\\n')
escaped = escaped.replace('\t', '\\t')
return escaped
def is_regional_language_code(language_code):
pattern = r'^[a-zA-Z]{2}([-_][a-zA-Z0-9]{2})?$'
return re.match(pattern, language_code)
def is_bcp47_language_code(language_code):
pattern = r'^[a-zA-Z]{1,8}([-_][a-zA-Z0-9]{1,8})*$'
return re.match(pattern, language_code)
def get_lang_qualifier(file):
'''Generates <qualifier> for res/values-<qualifier> directory as per the specs given here:
https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources
'''
if is_regional_language_code(file):
lang_qualifier = file.replace('_', '-r')
elif is_bcp47_language_code(file):
subtags = re.split('[-_]', file)
lang_qualifier = '+'.join(subtags)
lang_qualifier = 'b+' + lang_qualifier
else:
raise Exception(f"Invalid language code: {file}")
return lang_qualifier
def generate_translated_xml(file):
tree = ET.parse(os.path.join(BASE_PATH, 'dists/android/res/values/strings.xml'))
root = tree.getroot()
translations = extract_translations(file)
for string in root.findall('string'):
if string.text in translations:
string.text = escape_special_characters(translations[string.text])
else:
root.remove(string)
ET.indent(tree, ' ')
dir = os.path.join(BASE_PATH, 'dists/android/res/values-' + get_lang_qualifier(file))
if not os.path.exists(dir):
os.makedirs(dir)
tree.write(os.path.join(dir, 'strings.xml'), encoding='utf-8', xml_declaration=True)
def get_po_files():
po_file_names = []
for filename in os.listdir(os.path.join(BASE_PATH, 'po')):
if not filename.endswith('.po'):
continue
# This skips be-tarask file because there is a bug with be-tarask that gives this compile error:
# AAPT: error: failed to deserialize resource table: configuration has invalid locale 'be-'.
# See the open issue here: https://issuetracker.google.com/issues/234820481
if (filename == 'be-tarask.po'):
continue
po_file_names.append(os.path.splitext(filename)[0])
return po_file_names
def main():
generate_fake_cpp()
po_file_names = get_po_files()
for file in po_file_names:
generate_translated_xml(file)
if __name__ == '__main__':
main()