First Commit

This commit is contained in:
2025-12-18 16:28:50 +07:00
commit 8c3e4f491f
9974 changed files with 396488 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
# Localazy
Localazy is used to host the source strings and their translations.
<!--- TOC -->
* [Localazy project](#localazy-project)
* [Key naming rules](#key-naming-rules)
* [Special suffixes](#special-suffixes)
* [Placeholders](#placeholders)
* [CLI Installation](#cli-installation)
* [Download translations](#download-translations)
* [Add translations to a specific module](#add-translations-to-a-specific-module)
<!--- END -->
## Localazy project
[![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element)
To add new strings, or to translate existing strings, go the the Localazy project: [https://localazy.com/p/element](https://localazy.com/p/element). Please follow the key naming rules (see below).
Never edit manually the files `localazy.xml` or `translations.xml`!.
### Key naming rules
For code clarity and in order to download strings to the correct module, here are some naming rules to follow as much as possible:
- Keys for common strings, i.e. strings that can be used at multiple places must start by `action.` if this is a verb, or `common.` if not;
- Keys for common accessibility strings must start by `a11y.`. Example: `a11y.hide_password`;
- Keys for common strings should be named to match the string. Example: `action.copy_link` for the string `Copy link`;
- When creating common strings, make sure to enable "Use dot (.) to create nested keys";
- Keys for strings used in a single screen must start with `screen_` followed by the screen name, followed by a free name. Example: `screen_onboarding_welcome_title`;
- Keys can have `_title` or `_subtitle` suffixes. Example: `screen_onboarding_welcome_title`, `screen_change_server_subtitle`;
- For dialogs, keys can have `_dialog_title`, `_dialog_content`, and `_dialog_submit` suffixes. Example: `screen_signout_confirmation_dialog_title`, `screen_signout_confirmation_dialog_content`, `screen_signout_confirmation_dialog_submit`;
- `a11y.` pattern can be used for strings that are only used for accessibility. Example: `a11y.hide_password`, `screen_roomlist_a11y_create_message`;
- Strings for error message can start by `error_`, or contain `_error_` if used in a specific screen only. Example: `error_some_messages_have_not_been_sent`, `screen_change_server_error_invalid_homeserver`;
*Note*: those rules applies for `strings` and for `plurals`.
#### Special suffixes
- if a key is suffixed by `_ios`, it will not be imported in the Android project;
- if a key is suffixed by `_android`, it will not be imported in the iOS project.
So feel free to use those suffixes when necessary for instance when the string content is referring to something related to Android only, or iOS only.
#### Placeholders
Placeholders should have the form `%1$s`, `%1$d`, etc.. Please use numbered placeholders. Note that Localazy will take care of converting the placeholder to Android (-> `%1$s`) and iOS specific format (-> `%1$@`). Ideally add a comment on Localazy to explain with what the placeholder(s) will be replaced at runtime.
## CLI Installation
To install the Localazy client, follow the instructions from [here](https://localazy.com/docs/cli/installation).
## Download translations
In the root folder of the project, run:
```shell
./tools/localazy/downloadStrings.sh
```
It will update all the `localazy.xml` resource files. In case of merge conflicts, just erase the files and download again using the script.
To also include the translations, i.e. the `translations.xml` files, add `--all` argument:
```shell
./tools/localazy/downloadStrings.sh --all
```
## Add translations to a specific module
Edit the file [config.json](./config.json) to add a new module, or add a new item in `includeRegex` arrays, then run the script again to see the effect.
[generateLocalazyConfig.py](generateLocalazyConfig.py) is the Python script that convert `config.json` to a localazy configuration file. Generally you should not edit this file.
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# Copyright (c) 2025 Element Creations Ltd.
# Copyright 2024, 2025 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
# Please see LICENSE files in the repository root for full details.
import re
import sys
from xml.dom import minidom
file = sys.argv[1]
# Dict of forbidden terms, with exceptions for some String name
# Keys are the terms, values are the exceptions.
forbiddenTerms = {
r"\bElement\b": [
# Those 2 strings are only used in debug version
"screen_advanced_settings_element_call_base_url",
"screen_advanced_settings_element_call_base_url_description",
# only used for element.io homeserver, so it's fine
"screen_server_confirmation_message_login_element_dot_io",
# "Be in your element", will probably be changed on the forks, so we can ignore.
"screen_onboarding_welcome_title",
# Contains "Element Call"
"screen_incoming_call_subtitle_android",
"call_invalid_audio_device_bluetooth_devices_disabled",
# Contains "Element X"
"screen_room_timeline_legacy_call",
# We explicitly want to mention Element Pro in these 2:
"screen_change_server_error_element_pro_required_title",
"screen_change_server_error_element_pro_required_message",
]
}
content = minidom.parse(file)
errors = []
### Strings
for elem in content.getElementsByTagName('string'):
name = elem.attributes['name'].value
# Continue if value is empty
child = elem.firstChild
if child is None:
# Should not happen
continue
value = child.nodeValue
# If value contains a forbidden term, add the error to errors
for (term, exceptions) in forbiddenTerms.items():
matches = re.search(term, value)
if matches and name not in exceptions:
errors.append('Forbidden term "' + term + '" in string: "' + name + '": ' + value)
### Plurals
for elem in content.getElementsByTagName('plurals'):
name = elem.attributes['name'].value
for it in elem.childNodes:
if it.nodeType != it.ELEMENT_NODE:
continue
# Continue if value is empty
child = it.firstChild
if child is None:
# Should not happen
continue
value = child.nodeValue
# If value contains a forbidden term, add the error to errors
for (term, exceptions) in forbiddenTerms.items():
matches = re.search(term, value)
if matches and name not in exceptions:
errors.append('Forbidden term "' + term + '" in plural: "' + name + '": ' + value)
# If errors is not empty print the report
if errors:
print('Error(s) in file ' + file + ":", file=sys.stderr)
for error in errors:
print(" - " + error, file=sys.stderr)
sys.exit(1)
+389
View File
@@ -0,0 +1,389 @@
{
"modules" : [
{
"name" : ":appnav",
"includeRegex" : [
"banner\\.migrate_to_native_sliding_sync\\.force_logout.title",
"banner\\.migrate_to_native_sliding_sync\\.action",
"banner\\.migrate_to_native_sliding_sync\\.app_force_logout\\.title"
]
},
{
"name" : ":features:rageshake:impl",
"includeRegex" : [
"screen_bug_report_.*"
]
},
{
"name" : ":features:rageshake:api",
"includeRegex" : [
"crash_detection_.*",
"rageshake_detection_.*",
"settings_rageshake.*"
]
},
{
"name" : ":features:announcement:impl",
"includeRegex" : [
"screen\\.space_announcement\\..*"
]
},
{
"name" : ":features:logout:impl",
"includeRegex" : [
"screen_signout_.*"
]
},
{
"name" : ":features:deactivation:impl",
"includeRegex" : [
"screen_deactivate_account_.*"
]
},
{
"name" : ":features:roomaliasresolver:impl",
"includeRegex" : [
"screen_room_alias_resolver_.*",
"screen.join_room.loading_alert_title"
]
},
{
"name" : ":features:signedout:impl",
"includeRegex" : [
"screen_signed_out_.*"
]
},
{
"name" : ":features:invite:impl",
"includeRegex" : [
"screen_invites_.*",
"screen\\.join_room\\.decline_and_block_.*",
"screen\\.decline_and_block\\..*"
]
},
{
"name" : ":features:createroom:impl",
"includeRegex" : [
"screen_create_room_.*",
"screen\\.create_room\\..*"
]
},
{
"name" : ":features:startchat:impl",
"includeRegex" : [
"screen_start_chat_.*",
"screen\\.start_chat\\..*",
"screen_room_directory_search_title",
"screen_create_room_action_create_room"
]
},
{
"name" : ":features:verifysession:impl",
"includeRegex" : [
"screen_session_verification_.*",
"screen_signout_in_progress_dialog_content",
"screen_identity_.*"
]
},
{
"name" : ":libraries:textcomposer:impl",
"includeRegex" : [
"rich_text_editor.*",
".*voice_message_tooltip",
"screen\\.media_upload_preview.caption_warning"
]
},
{
"name" : ":libraries:dateformatter:impl",
"includeRegex" : [
"common\\.date\\..*"
]
},
{
"name" : ":libraries:permissions:api",
"includeRegex" : [
"dialog\\.permission_.*"
]
},
{
"name" : ":libraries:androidutils",
"includeRegex" : [
"error_no_compatible_app_found"
]
},
{
"name" : ":libraries:mediaviewer:impl",
"includeRegex" : [
"screen\\.media_details\\..*",
"screen_media_browser_.*"
]
},
{
"name" : ":libraries:eventformatter:impl",
"includeRegex" : [
"state_event_.*"
]
},
{
"name" : ":libraries:push:impl",
"includeRegex" : [
"push_.*",
"notification_.*",
"notification\\..*",
"troubleshoot_notifications\\.test_blocked_users\\..*",
"troubleshoot_notifications_test_current_push_provider.*",
"troubleshoot_notifications_test_detect_push_provider.*",
"troubleshoot_notifications_test_display_notification_.*",
"troubleshoot_notifications_test_push_loop_back_.*"
]
},
{
"name" : ":libraries:permissions:impl",
"includeRegex" : [
"troubleshoot_notifications_test_check_permission_.*"
]
},
{
"name" : ":libraries:pushproviders:firebase",
"includeRegex" : [
"troubleshoot_notifications_test_firebase_.*"
]
},
{
"name" : ":libraries:pushproviders:unifiedpush",
"includeRegex" : [
"troubleshoot_notifications_test_unified_push_.*"
]
},
{
"name" : ":features:login:impl",
"includeRegex" : [
"screen_onboarding_.*",
"screen_login_.*",
"screen_server_confirmation_.*",
"screen_change_server_.*",
"screen_change_account_provider_.*",
"screen_create_account_.*",
"screen_account_provider_.*",
"screen_waitlist_.*",
"screen_qr_code_login_.*"
]
},
{
"name" : ":features:leaveroom:api",
"includeRegex" : [
"leave_room_alert_.*",
"leave_conversation_alert_.*"
]
},
{
"name" : ":features:home:impl",
"includeRegex" : [
"screen\\.home\\..*",
"screen_roomlist_.*",
"screen\\.roomlist\\..*",
"session_verification_banner_.*",
"confirm_recovery_key_banner_.*",
"banner\\.set_up_recovery\\..*",
"banner\\.battery_optimization\\..*",
"banner\\.new_sound\\..*",
"full_screen_intent_banner_.*",
"screen_migration_.*",
"screen_invites_.*"
]
},
{
"name" : ":features:roomdetails:impl",
"includeRegex" : [
"screen_room_details_.*",
"screen\\.room_details\\..*",
"screen_room_member_list_.*",
"screen\\.room_member_list\\..*",
"screen_room_notification_settings_.*",
"screen_notification_settings_edit_failed_updating_default_mode",
"screen_polls_history_title",
"screen_notification_settings_mentions_only_disclaimer",
"screen_room_change_.*",
"screen_room_roles_.*",
"screen\\.edit_room_address\\..*",
"screen\\.security_and_privacy\\..*"
]
},
{
"name" : ":features:space:impl",
"includeRegex" : [
"screen\\.leave_space\\..*",
"screen\\.space_settings\\..*"
]
},
{
"name" : ":features:userprofile:shared",
"includeRegex" : [
"screen_start_chat_error_starting_chat",
"screen_dm_details_.*",
"screen_room_member_details_.*"
]
},
{
"name" : ":features:invitepeople:impl",
"includeRegex" : [
"screen\\.invite_users\\..*"
]
},
{
"name" : ":features:messages:impl",
"includeRegex" : [
"emoji_picker_category_.*",
"screen_report_content_.*",
"screen_room_attachment.*",
"screen_room_encrypted.*",
"screen_room_invite.*",
"screen\\.room\\.mention.*",
"screen_room_message.*",
"screen_room_retry.*",
"screen_room_timeline.*",
"screen\\.room_timeline.*",
"screen_room_typing.*",
"screen\\.media_upload.*"
]
},
{
"name" : ":features:analytics:impl",
"includeRegex" : [
"screen_analytics_prompt.*"
]
},
{
"name" : ":features:analytics:api",
"includeRegex" : [
"screen_analytics_settings_.*"
]
},
{
"name" : ":features:ftue:impl",
"includeRegex" : [
"screen_welcome_.*",
"screen_notification_optin_.*",
"screen_identity_.*",
"screen_session_verification_enter_recovery_key"
]
},
{
"name" : ":features:poll:impl",
"includeRegex" : [
"screen_create_poll_.*",
"screen_edit_poll_.*",
"screen_polls_history_.*"
]
},
{
"name" : ":features:poll:api",
"includeRegex" : [
"a11y\\.polls\\..*"
]
},
{
"name" : ":features:securebackup:impl",
"includeRegex" : [
"screen_chat_backup_.*",
"screen_key_backup_disable_.*",
"screen_recovery_key_.*",
"screen_create_new_recovery_key_.*",
"screen_encryption_reset.*",
"screen_reset_encryption.*",
"screen\\.reset_encryption.*"
]
},
{
"name" : ":features:preferences:impl",
"includeRegex" : [
"screen_advanced_settings_.*",
"screen\\.advanced_settings\\..*",
"screen_edit_profile_.*",
"screen_notification_settings_.*",
"screen_blocked_users_.*",
"full_screen_intent_banner_.*",
"troubleshoot_notifications_entry_point_.*",
"screen\\.labs\\..*"
]
},
{
"name" : ":libraries:troubleshoot:impl",
"includeRegex" : [
"troubleshoot_notifications_screen_.*",
"screen\\.push_history\\..*"
]
},
{
"name" : ":libraries:matrixui",
"includeRegex" : [
"screen_invites_invited_you",
"screen\\.bottom_sheet\\.create_dm\\..*"
]
},
{
"name" : ":features:call:impl",
"includeRegex" : [
"call_.*",
"screen_incoming_call.*"
]
},
{
"name" : ":features:lockscreen:impl",
"includeRegex" : [
"screen_app_lock_.*",
"screen_signout_in_progress_dialog_content"
]
},
{
"name" : ":features:roomdirectory:impl",
"includeRegex" : [
"screen_room_directory_.*"
]
},
{
"name" : ":features:joinroom:impl",
"includeRegex" : [
"screen_join_room_.*",
"screen\\.join_room\\..*"
]
},
{
"name" : ":features:knockrequests:impl",
"includeRegex" : [
"screen\\.knock_requests_list\\..*",
"screen\\.room\\.single_knock_request.*",
"screen\\.room\\.multiple_knock_requests.*"
]
},
{
"name" : ":features:reportroom:impl",
"includeRegex" : [
"screen\\.report_room\\..*"
]
},
{
"name" : ":features:roommembermoderation:impl",
"includeRegex" : [
"screen\\.bottom_sheet\\.manage_room_member\\..*"
]
},
{
"name" : ":features:rolesandpermissions:impl",
"includeRegex" : [
"screen_room_change_.*",
"screen_room_roles_.*",
"screen\\.room_roles_and_permissions\\..*",
"screen_room_member_list.*",
"screen\\.room_member_list\\..*"
]
},
{
"name" : ":features:securityandprivacy:impl",
"includeRegex" : [
"screen\\.edit_room_address\\..*",
"screen\\.security_and_privacy\\..*"
]
}
]
}
+48
View File
@@ -0,0 +1,48 @@
#! /bin/bash
# Copyright (c) 2025 Element Creations Ltd.
# Copyright 2023-2024 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
# Please see LICENSE files in the repository root for full details.
set -e
if [[ $1 == "--all" ]]; then
echo "Note: I will update all the files."
allFiles=1
else
echo "Note: I will update only the English files."
allFiles=0
fi
echo "Generating the configuration file for localazy..."
python3 ./tools/localazy/generateLocalazyConfig.py $allFiles
echo "Deleting all existing localazy.xml files..."
find . -name 'localazy.xml' -delete
if [[ $allFiles == 1 ]]; then
echo "Deleting all existing translations.xml files..."
find . -name 'translations.xml' -delete
fi
echo "Importing the strings..."
localazy download --config ./tools/localazy/localazy.json
echo "Removing the generated config"
rm ./tools/localazy/localazy.json
echo "Formatting the resources files..."
find . -name 'localazy.xml' -exec ./tools/localazy/formatXmlResourcesFile.py {} \;
if [[ $allFiles == 1 ]]; then
find . -name 'translations.xml' -exec ./tools/localazy/formatXmlResourcesFile.py {} \;
fi
echo "Checking forbidden terms..."
find . -name 'localazy.xml' -exec ./tools/localazy/checkForbiddenTerms.py {} \;
if [[ $allFiles == 1 ]]; then
find . -name 'translations.xml' -exec ./tools/localazy/checkForbiddenTerms.py {} \;
fi
echo "Success!"
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
# Copyright (c) 2025 Element Creations Ltd.
# Copyright 2024, 2025 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
# Please see LICENSE files in the repository root for full details.
import re
import sys
from xml.dom import minidom
file = sys.argv[1]
content = minidom.parse(file)
# sort content by value of tag name
newContent = minidom.Document()
resources = newContent.createElement('resources')
resources.setAttribute('xmlns:xliff', 'urn:oasis:names:tc:xliff:document:1.2')
newContent.appendChild(resources)
resource = dict()
### Strings
for elem in content.getElementsByTagName('string'):
name = elem.attributes['name'].value
# Continue if value is empty
child = elem.firstChild
if child is None:
# Print an error to stderr
print('Warning: Empty content for string: ' + name + " in file " + file, file=sys.stderr)
continue
value = child.nodeValue
# Continue if string is empty
if value == '""':
# Print an error to stderr
print('Warning: Empty string value for string: ' + name + " in file " + file, file=sys.stderr)
continue
resource[name] = elem.cloneNode(True)
### Plurals
for elem in content.getElementsByTagName('plurals'):
plural = newContent.createElement('plurals')
name = elem.attributes['name'].value
plural.setAttribute('name', name)
for it in elem.childNodes:
if it.nodeType != it.ELEMENT_NODE:
continue
# Continue if value is empty
child = it.firstChild
if child is None:
# Print an error to stderr
print('Warning: Empty content for plurals: ' + name + " in file " + file, file=sys.stderr)
continue
value = child.nodeValue
# Continue if string is empty
if value == '""':
# Print an error to stderr
print('Warning: Empty item value for plurals: ' + name + " in file " + file, file=sys.stderr)
continue
plural.appendChild(it.cloneNode(True))
if plural.hasChildNodes():
resource[name] = plural
for key in sorted(resource.keys()):
resources.appendChild(resource[key])
result = newContent.toprettyxml(indent=" ") \
.replace('<?xml version="1.0" ?>', '<?xml version="1.0" encoding="utf-8"?>') \
.replace('&quot;', '"') \
.replace('...', '')
## Replace space by unbreakable space before punctuation
result = re.sub(r" ([\?\!\:…])", r" \1", result)
# Special treatment for French wording
if 'values-fr' in file:
## Replace ' with
result = re.sub(r"([cdjlmnsu])\\\'", r"\1", result, flags=re.IGNORECASE)
with open(file, "w") as text_file:
text_file.write(result)
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
# Copyright (c) 2025 Element Creations Ltd.
# Copyright 2024, 2025 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
# Please see LICENSE files in the repository root for full details.
import json
import sys
# Read the config.json file
with open('./tools/localazy/config.json', 'r') as f:
config = json.load(f)
allFiles = sys.argv[1] == "1"
# Convert a module name to a path
# Ex: ":features:verifysession:impl" => "features/verifysession/impl"
def convertModuleToPath(name):
return name[1:].replace(":", "/")
# Regex that will be excluded from the Android project, you may add items here if necessary.
regexToAlwaysExclude = [
"Notification",
".*_ios"
]
baseAction = {
"type": "android",
# Replacement done in all string values
"replacements": {
"...": ""
},
"params": {
"force_underscore": "yes"
}
}
# Store all regex specific to module, to exclude the corresponding key from the common string module
allRegexToExcludeFromMainModule = []
# All actions that will be serialized in the localazy config
allActions = []
# Iterating on the config
for entry in config["modules"]:
# Create action for the default language
excludeRegex = regexToAlwaysExclude
if "excludeRegex" in entry:
excludeRegex += entry["excludeRegex"]
action = baseAction | {
"output": convertModuleToPath(entry["name"]) + "/src/main/res/values/localazy.xml",
"includeKeys": list(map(lambda i: "REGEX:" + i, entry["includeRegex"])),
"excludeKeys": list(map(lambda i: "REGEX:" + i, excludeRegex)),
"conditions": [
"equals: ${langAndroidResNoScript}, en | equals: ${file}, content.json"
]
}
# print(action)
allActions.append(action)
# Create action for the translations
if allFiles:
actionTranslation = baseAction | {
"output": convertModuleToPath(entry["name"]) + "/src/main/res/values-${langAndroidResNoScript}/translations.xml",
"includeKeys": list(map(lambda i: "REGEX:" + i, entry["includeRegex"])),
"excludeKeys": list(map(lambda i: "REGEX:" + i, excludeRegex)),
"conditions": [
"!equals: ${langAndroidResNoScript}, en | equals: ${file}, content.json"
],
"langAliases": {
"id": "in"
}
}
allActions.append(actionTranslation)
allRegexToExcludeFromMainModule.extend(entry["includeRegex"])
# Append configuration for the main string module: default language
mainAction = baseAction | {
"output": "libraries/ui-strings/src/main/res/values/localazy.xml",
"excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)),
"conditions": [
"equals: ${langAndroidResNoScript}, en | equals: ${file}, content.json"
]
}
# print(mainAction)
allActions.append(mainAction)
if allFiles:
# Append configuration for the main string module: translations
mainActionTranslation = baseAction | {
"output": "libraries/ui-strings/src/main/res/values-${langAndroidResNoScript}/translations.xml",
"excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)),
"conditions": [
"!equals: ${langAndroidResNoScript}, en | equals: ${file}, content.json"
],
"langAliases": {
"id": "in"
}
}
allActions.append(mainActionTranslation)
# Generate the configuration for localazy
result = {
"readKey": "a7876306080832595063-aa37154bb3772f6146890fca868d155b2228b492c56c91f67abdcdfb74d6142d",
"conversion": {
"actions": allActions
}
}
# Json serialization
with open('./tools/localazy/localazy.json', 'w') as json_file:
json.dump(result, json_file, indent=4, sort_keys=True)
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
# Copyright (c) 2025 Element Creations Ltd.
# Copyright 2024, 2025 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
# Please see LICENSE files in the repository root for full details.
import os
import subprocess
def getLocalesFromLocalazy():
command = subprocess.run(
["localazy languages --read-key a7876306080832595063-aa37154bb3772f6146890fca868d155b2228b492c56c91f67abdcdfb74d6142d --csv"],
shell=True,
capture_output=True,
text=True,
)
data = command.stdout
result = []
for line in data.split("\n"):
if line:
line = line.split(",")
if (line[6] == "true"):
result.append(line[0])
return sorted(result)
def normalizeForResourceConfigurations(locale):
match locale:
case "id":
return "in"
case "zh_TW#Hant":
return "zh-rTW"
case "pt_BR":
return "pt-rBR"
case "zh#Hans":
return "zh-rCN"
case "en_US":
return "en-rUS"
case _:
return locale
def normalizeForLocalConfig(locale):
match locale:
case "id":
return "in"
case "zh_TW#Hant":
return "zh-TW"
case "zh#Hans":
return "zh-CN"
case _:
return locale
def generateLocaleFile(locales, file):
with open("plugins/src/main/kotlin/extension/locales.kt", "w") as f:
f.write("// File generated by " + file + ", do not edit\n\n")
f.write("package extension\n\n")
f.write("val locales = setOf(\n")
for locale in locales:
f.write(" \"" + normalizeForResourceConfigurations(locale) + "\",\n")
f.write(")\n")
def generateLocalesConfigFile(locales, file):
with open("app/src/main/res/xml/locales_config.xml", "w") as f:
f.write("<!-- File generated by " + file + ", do not edit -->\n")
f.write('<locale-config xmlns:android="http://schemas.android.com/apk/res/android">\n')
for locale in locales:
f.write(" <locale android:name=\"" + normalizeForLocalConfig(locale) + "\"/>\n")
f.write("</locale-config>\n")
def main():
file = os.path.basename(__file__)
locales = getLocalesFromLocalazy()
generateLocaleFile(locales, file)
generateLocalesConfigFile(locales, file)
if __name__ == "__main__":
main()