Skip to content

Commit 5b6775f

Browse files
authored
Merge pull request #15081 from rtibblesbot/issue-15074-dac7e5
Localize Android installer UI through Crowdin
2 parents e3313d0 + 4b6f279 commit 5b6775f

9 files changed

Lines changed: 119 additions & 252 deletions

File tree

.github/workflows/platform-android-build_apk.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,14 @@ jobs:
124124
name: ${{ inputs.tar-file-name }}
125125
path: platforms/android/tar
126126
# Install the member's Play Store build tooling from the shared workspace
127-
# lock into the root .venv that Gradle's buildPython targets, then add the
128-
# Chaquopy cext deps (kept out of the lock) that create_strings.py needs.
127+
# lock into the root .venv that Gradle's buildPython targets.
129128
# Pin the venv to Python 3.10 so it matches Chaquopy's embedded runtime
130129
# (app/build.gradle `python { version = "3.10" }`); buildPython must be the
131130
# same minor version or `installDebugPythonRequirements` rejects it.
132131
- name: Install dependencies
133132
working-directory: ${{ github.workspace }}
134133
run: |
135134
uv sync --package kolibri-installer-android --extra build --python 3.10
136-
uv pip install -r platforms/android/requirements.txt
137135
- name: Build the aab
138136
if: ${{ inputs.release }}
139137
env:

build_tools/i18n/generate_mapping.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
11
#!/usr/bin/env python3
22
"""
3-
Script to generate a mapping from crowdin_code to Django locale code.
4-
Reads language_info.json and uses our copy of Django's locale utilities
5-
to convert intl_code to locale format.
3+
Generate crowdin_code mappings (Django locale, installer, Android qualifier) for crowdin.yml.
64
"""
75

86
from build_tools.i18n.utils import available_languages
97
from build_tools.i18n.utils import to_locale
108

9+
# Script subtags (es-419, zh-hans) can't be derived by to_android_locale's
10+
# region logic (segment 2 -> -rXX), so list them explicitly here, keyed by
11+
# intl_code. Add any future script-suffixed intl code here too.
12+
ANDROID_LOCALE_OVERRIDES = {
13+
"es-419": "b+es+419",
14+
"zh-hans": "zh",
15+
}
1116

12-
def get_language_mapping():
13-
"""
14-
Read language_info.json and return a mapping from crowdin_code to Django locale code.
1517

16-
Returns:
17-
dict: Mapping from crowdin_code to locale code (underscore format)
18-
"""
18+
def to_android_locale(intl_code):
19+
"""Convert a Kolibri intl code to an Android values-<code> qualifier."""
20+
if intl_code in ANDROID_LOCALE_OVERRIDES:
21+
return ANDROID_LOCALE_OVERRIDES[intl_code]
22+
parts = intl_code.split("-")
23+
if len(parts) == 1:
24+
return intl_code
25+
if len(parts) == 2:
26+
return f"{parts[0]}-r{parts[1].upper()}"
27+
raise ValueError(f"Invalid intl code: {intl_code}")
28+
29+
30+
def get_android_language_mapping():
31+
"""Map each crowdin_code to its Android values-<code> qualifier."""
32+
return {
33+
lang["crowdin_code"]: to_android_locale(lang["intl_code"])
34+
for lang in available_languages()
35+
}
36+
37+
38+
def get_language_mapping():
39+
"""Map each crowdin_code to its Django locale code (underscore format)."""
1940
# to_locale handles script suffixes like 'hans' correctly
2041
return {
2142
lang["crowdin_code"]: to_locale(lang["intl_code"])
@@ -30,15 +51,19 @@ def get_installer_language_mapping():
3051

3152

3253
def main():
33-
"""Print the underscore and installer (hyphen-lowercase) mappings for crowdin.yml."""
34-
mapping = get_language_mapping()
35-
54+
"""Print the Django-locale, Android, and installer mappings for crowdin.yml."""
3655
print("# Generated mapping from crowdin_code to Django locale code") # noqa: T201
3756
print("# Generated by build_tools/i18n/generate_mapping.py") # noqa: T201
38-
39-
for crowdin_code, locale_code in sorted(mapping.items()):
57+
for crowdin_code, locale_code in sorted(get_language_mapping().items()):
4058
print(f' "{crowdin_code}": "{locale_code}",') # noqa: T201
4159

60+
print() # noqa: T201
61+
print("# Generated mapping from crowdin_code to Android values-<code> qualifier") # noqa: T201
62+
print("# Generated by build_tools/i18n/generate_mapping.py") # noqa: T201
63+
print(' "android_code":') # noqa: T201
64+
for crowdin_code, android_code in sorted(get_android_language_mapping().items()):
65+
print(f' "{crowdin_code}": "{android_code}",') # noqa: T201
66+
4267
print(" # Installer (hyphen-lowercase) mapping — %locale% placeholder") # noqa: T201
4368
for crowdin_code, intl_code in sorted(get_installer_language_mapping().items()):
4469
print(f' "{crowdin_code}": "{intl_code}",') # noqa: T201

crowdin.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,13 @@ files: [
5454
# inline hyphen-lowercase %locale% map because installer/translations/definitions.py
5555
# keys the locale definitions by hyphen-lowercase codes (es-es, zh-hans). Both
5656
# maps derive from the same language_info.json via generate_mapping.py.
57-
{"source": "/platforms/desktop-app/src/kolibri_app/locales/en/LC_MESSAGES/wxapp.po", "update_option": "update_as_unapproved", "translation": "/platforms/desktop-app/src/kolibri_app/locales/%locale_with_underscore%/LC_MESSAGES/wxapp.po", "languages_mapping": *language_mapping}, {"source": "/platforms/desktop-app/installer/translations/locale/en/messages.po", "update_option": "update_as_unapproved", "translation": "/platforms/desktop-app/installer/translations/locale/%locale%/messages.po", "languages_mapping": {"locale": {"ach": "ach-ug", "ar": "ar", "bg": "bg-bg", "bn": "bn-bd", "de": "de", "el": "el", "en": "en", "es-ES": "es-es", "fa": "fa", "fr": "fr-fr", "fv": "ff-cm", "gu-IN": "gu-in", "ha": "ha", "hi": "hi-in", "ht": "ht", "id": "id", "it": "it", "ka": "ka", "km": "km", "ko": "ko", "la": "es-419", "mr": "mr", "my": "my", "ny": "ny", "pa-IN": "pa", "pt-BR": "pt-br", "pt-mz": "pt-mz", "sw-TZ": "sw-tz", "te": "te", "uk": "uk", "ur-PK": "ur-pk", "vi": "vi", "yo": "yo", "zh-CN": "zh-hans"}}}]
57+
{"source": "/platforms/desktop-app/src/kolibri_app/locales/en/LC_MESSAGES/wxapp.po", "update_option": "update_as_unapproved", "translation": "/platforms/desktop-app/src/kolibri_app/locales/%locale_with_underscore%/LC_MESSAGES/wxapp.po", "languages_mapping": *language_mapping}, {"source": "/platforms/desktop-app/installer/translations/locale/en/messages.po", "update_option": "update_as_unapproved", "translation": "/platforms/desktop-app/installer/translations/locale/%locale%/messages.po", "languages_mapping": {"locale": {"ach": "ach-ug", "ar": "ar", "bg": "bg-bg", "bn": "bn-bd", "de": "de", "el": "el", "en": "en", "es-ES": "es-es", "fa": "fa", "fr": "fr-fr", "fv": "ff-cm", "gu-IN": "gu-in", "ha": "ha", "hi": "hi-in", "ht": "ht", "id": "id", "it": "it", "ka": "ka", "km": "km", "ko": "ko", "la": "es-419", "mr": "mr", "my": "my", "ny": "ny", "pa-IN": "pa", "pt-BR": "pt-br", "pt-mz": "pt-mz", "sw-TZ": "sw-tz", "te": "te", "uk": "uk", "ur-PK": "ur-pk", "vi": "vi", "yo": "yo", "zh-CN": "zh-hans"}}},
58+
# Android installer UI strings (issue #15074). type: android forces Crowdin's
59+
# Android-resource parser (not .xml auto-detect), which honours
60+
# translatable="false" (so task_worker_process is not uploaded) and
61+
# Android-encodes downloads. %android_code% expands to the values-<code>
62+
# qualifier; the android_code mapping (generated by
63+
# build_tools/i18n/generate_mapping.py) overrides Crowdin's defaults so folders
64+
# match Kolibri's language set. dest keeps the flattened basename unique on
65+
# Crowdin under preserve_hierarchy: false.
66+
{"source": "/platforms/android/app/src/main/res/values/strings.xml", "type": "android", "dest": "/android_strings.xml", "translation": "/platforms/android/app/src/main/res/values-%android_code%/strings.xml", "update_option": "update_as_unapproved", "languages_mapping": {"android_code": {"ach": "ach-rUG", "ar": "ar", "bg": "bg-rBG", "bn": "bn-rBD", "de": "de", "el": "el", "en": "en", "es-ES": "es-rES", "fa": "fa", "fr": "fr-rFR", "fv": "ff-rCM", "gu-IN": "gu-rIN", "ha": "ha", "hi": "hi-rIN", "ht": "ht", "id": "id", "it": "it", "ka": "ka", "km": "km", "ko": "ko", "la": "b+es+419", "mr": "mr", "my": "my", "ny": "ny", "pa-IN": "pa", "pt-BR": "pt-rBR", "pt-mz": "pt-rMZ", "sw-TZ": "sw-rTZ", "te": "te", "uk": "uk", "ur-PK": "ur-rPK", "vi": "vi", "yo": "yo", "zh-CN": "zh"}}}]

platforms/android/.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# Generated source files
2-
app/src/main/python/strings.py
3-
app/src/main/res/values-*/strings.xml
4-
51
# Temporary files
62
tmpenv
73
tmphome/

platforms/android/app/build.gradle

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -142,43 +142,6 @@ if (extractedKolibriDir.exists() && extractedKolibriDir.isDirectory()) {
142142
println "Using default version (build Kolibri tar first for correct version)"
143143
}
144144

145-
// Task to generate Android resource strings from Kolibri translations
146-
abstract class GenerateKolibriStringsTask extends DefaultTask {
147-
@InputFile
148-
abstract RegularFileProperty getScriptFile()
149-
150-
@Input
151-
abstract Property<String> getPythonExecutable()
152-
153-
@OutputDirectory
154-
abstract DirectoryProperty getResDir()
155-
156-
@Inject
157-
abstract ExecOperations getExecOperations()
158-
159-
@TaskAction
160-
void generateStrings() {
161-
println "Generating Kolibri strings..."
162-
163-
def projectRoot = getScriptFile().get().asFile.parentFile.parentFile
164-
def pythonExe = getPythonExecutable().get()
165-
166-
execOperations.exec {
167-
workingDir projectRoot
168-
commandLine pythonExe, 'scripts/create_strings.py'
169-
}
170-
171-
println "Kolibri strings generated successfully"
172-
}
173-
}
174-
175-
tasks.register('generateKolibriStrings', GenerateKolibriStringsTask) {
176-
dependsOn streamlineKolibriTar
177-
getScriptFile().set(file('../scripts/create_strings.py'))
178-
getResDir().set(file('src/main/res'))
179-
getPythonExecutable().set(buildPythonExecutable)
180-
}
181-
182145
// Make sure Kolibri is extracted before Python requirements are generated
183146
afterEvaluate {
184147
tasks.matching { task ->
@@ -187,20 +150,6 @@ afterEvaluate {
187150
}.all { task ->
188151
task.dependsOn streamlineKolibriTar
189152
}
190-
191-
// Generate strings before resources are processed or mapped
192-
tasks.matching { task ->
193-
task.name.contains('generateDebugResources') ||
194-
task.name.contains('generateReleaseResources') ||
195-
task.name.contains('mergeDebugResources') ||
196-
task.name.contains('mergeReleaseResources') ||
197-
task.name.contains('mapDebugSourceSetPaths') ||
198-
task.name.contains('mapReleaseSourceSetPaths') ||
199-
task.name.contains('extractDeepLinksDebug') ||
200-
task.name.contains('extractDeepLinksRelease')
201-
}.all { task ->
202-
task.dependsOn generateKolibriStrings
203-
}
204153
}
205154

206155
android {

platforms/android/pyproject.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ known-first-party = ["kolibri"]
4343
# on sys.path) that must precede `import zeroconf`, so it is neither unused (F401)
4444
# nor safe to reorder (I001).
4545
"app/src/main/python/monkey_patch_zeroconf.py" = ["F401", "I001"]
46-
# create_resource_files walks the locale tree inline; splitting it up would not
47-
# make the string-generation flow any clearer.
48-
"scripts/create_strings.py" = ["C901"]
4946

5047
[tool.pytest.ini_options]
5148
testpaths = ["scripts/tests"]

platforms/android/scripts/create_strings.py

Lines changed: 0 additions & 176 deletions
This file was deleted.

0 commit comments

Comments
 (0)