-
Notifications
You must be signed in to change notification settings - Fork 15
Add multi-language support to Windows installer #183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add multi-language support to Windows installer #183
Conversation
12267e8 to
c22e54e
Compare
224a5ef to
8cda0e6
Compare
c22cad8 to
462d25d
Compare
rtibbles
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this has most of the right pieces in place! Just a couple of clarifications that came to mind - and I'm not entirely sure we need all of the code here with the right workflow in place.
| @@ -0,0 +1,173 @@ | |||
| """ | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this script. The workflow we would most likely use is to take the English isl file, convert it to a po file then upload it to Crowdin (the translation platform that we use).
We would then download the translated po files for each language, and convert those to isl files for each language. I think your two conversion scripts should handle all that is needed in that case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
take the English isl file, convert it to a po file then upload it to Crowdin
If we don't use create_new_language.py, then the translator would have to re-translate even the standard strings that InnoSetup already provides (for languages like spanish).
Instead if we use create_new_language.py, then only the custom strings would need to be translated.
Do I miss something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Crowdin lets you upload existing translations for a source as well - so if we have the original inno translations, we can convert them to a po file for upload.
My basic thought is that I'd like us to specify which languages we want to support, and then conditionally upload already existing translations for those languages for which translations already exist. It is also possible that I'm just misreading the workflow you're intending.
installer/translations/English.isl
Outdated
| @@ -0,0 +1,305 @@ | |||
| ; Master English messages for Kolibri Installer | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be easier to use the locale code as the filename en.isl rather than the language name? Especially as we have the language name encoded in here anyway!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, create_new_language.py uses the language name (e.g. Spanish) to find the packaged translation in C:\Program Files (x86)\Inno Setup 6\Languages.
We could modify the create_new_language.py script to take a locale code as input, and then use an internal dictionary to look up the Inno Setup Filename it needs to find.
Something like that
LOCALE_TO_INNO_NAME = {
"de": "German",
"es": "Spanish",
"fr": "French",
"it": "Italian",
# ...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that works for me - we end up doing this kind of mapping quite a lot for translations, because every translation system uses something different. We use the locale (e.g. es_ES) canonically, because it is the most specific, whereas just saying "Spanish" is very ambiguous - is that Spanish from Spain, Spanish from Argentina, Spanish from Latin America?
So, essentially for each locale code that we want to support, we would also make a note of which Inno Setup language name we would pull translations from.
installer/translations/English.isl
Outdated
|
|
||
| [LangOptions] | ||
| languagename=English | ||
| languageid=$0409 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do we find out these values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can be found here: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
I was thinking of adding this to the readme, when this PR is complete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be great - I suppose we could even add this information to the locale lookup object that we're defining above:
LOCALE_INFO = {
"de": {
"inno_setup_name": "German",
"ms_languageid": "0x0007",
},
# ...
}
If we wanted to get really fancy, we could fetch the page and parse the HTML table - but that seems very unneccessary!
| [LangOptions] | ||
| languagename=English | ||
| languageid=$0409 | ||
| languagecodepage=0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does languagecodepage mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It specifies the Windows ANSI code page that the language file text is encoded in. It tells Inno Setup how to correctly read and display the localized strings if the file isn’t in Unicode (UTF-8).
However how that the files are UTF-8, its not needed.
We can either remove it when creating a new language or maybe we can leave it as is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if we can be sure to encode in UTF-8 that seems like the best solution!
| @@ -0,0 +1,147 @@ | |||
| """ | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the main distinction to keep in mind is that we treat English as the "source", and all other languages as the "translations".
So, the workflow is, we upload every message we need in English in a po file - but then for some languages we will essentially have 'pre-filled' translations based on what InnoSetup already provides (either from its built in translations or community supported translations).
This commit refactors the Windows installer to support multiple languages and improves the build system's organization. User-facing strings are now externalized from `kolibri.iss` into `.isl` language files, enabling a language selection dialog on startup. All installer-related files (the script, translations, and dependencies) have been consolidated into the `installer/` directory. Major changes: - Added new translation management scripts (`create_new_language.py`, `update_from_inno_default.py`) to streamline localization. - Introduced `new-language` and `update-translations` Makefile targets to automate the translation workflow. - Updated the `Makefile` and PyInstaller spec to use the new `installer/` directory structure.
This commit introduces a translation workflow for the Windows installer using `.po` files. This replaces the previous method of directly editing `.isl` files, making the localization process more accessible and manageable for translators. - PO/ISL Conversion: Added `isl_to_po.py` and `po_to_isl.py` scripts to convert between Inno Setup's `.isl` format and the standard `.po` format. - Makefile Integration: - Created a `translations-export` target to generate `.po` files from existing `.isl` files for translators, using the `isl_to_po.py` script. - Added a `translations-compile` target to convert all `.po` files back into `.isl` format, using the `po_to_isl.py` script. - The `build-installer-windows` process now automatically runs `translations-compile`, ensuring the latest translations are always included in the build. - Initial German PO File: Added `German.po` to demonstrate the new workflow. - Improved Scaffolding: The `create_new_language.py` script has been updated to better handle the creation of new language files, ensuring custom messages are correctly scaffolded for translation.
462d25d to
e0ed8f3
Compare
…tion config
Changes:
- Renamed translation files and directories to use ISO codes (e.g., `es_ES` instead of `Spanish`), removing ambiguity.
- Added `definitions.py` as a single source of truth for Microsoft Language IDs and Inno Setup names, removing the need to manually configure `[LangOptions]`.
- Workflow Refactor:
- Removed `create_new_language.py`, its logic is now integrated into `isl_to_po.py` via the `new-language` target.
- Updated `po_to_isl.py` to automatically add the correct Language ID during compilation.
- Updated `Makefile` and `kolibri.iss` to support the new `translations/locale/<code >/` directory structure.
ef5d319 to
a1039f6
Compare
rtibbles
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking pretty good to me! I'll do a quick test locally to make sure I can follow the workflow.
|
Naturally, when I said I would do this... I forgot the fact that I don't have a Windows dev machine, so there are significant parts of the workflow that I can't actually do! @Dimi20cen as a final step here, could you add all the supported languages for Kolibri - you can see the full list of codes here: https://github.com/learningequality/kolibri/blob/develop/kolibri/utils/i18n.py#L47 If you need any more information about the languages, then this JSON file should have it: https://github.com/learningequality/kolibri/blob/develop/kolibri/locale/language_info.json Feel free to just focus on the intl-codes for now - I can add the extra mapping for going into the crowdin language code space! |
- Update `definitions.py` with native display names, custom fonts, and RTL flags. - Refactor locale codes (e.g., `de_DE` -> `de`) for consistency with kolbri intl-codes - Update `po_to_isl.py` to generate `LangOptions` based on new metadata. - Update `kolibri.iss` with the revised language list.
rtibbles
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is looking good, I'll have to run this on my windows machine to be absolutely sure, but I think it might be simpler just to merge this and iterate from there!
Summary
This PR restores multi-language support to the Windows installer, addressing a regression from our legacy version. It implements a modern, industry-standard translation workflow using .po files and ISO locale codes.
All user-facing strings have been externalized from kolibri.iss into language files. Additionally, all installer related files (the main script, translations, and dependencies) have been moved into a new
installer/directory for better organization.Changes
kolibri.issinto separate.islfiles. The system now uses ISO locale codes (e.g.,es_ES,de_DE) instead of English language names.definitions.pyto map ISO codes to their corresponding Inno Setup language names and Microsoft Language IDs. This automates the configuration of[LangOptions]and removes the need for manual ID lookup.isl_to_po.py: Converts Inno Setup files to.po. When scaffolding a new language, it automatically detects and merges standard Inno Setup translations (e.g., "Next >", "Cancel") so translators only need to focus on Kolibri-specific strings.po_to_isl.py: Compiles translated.pofiles back into.islformat for the installer, automatically injecting the correct Language IDs from the definitions file.build-installer-windowscommand automatically compiles all.pofiles found ininstaller/translations/locale/into the required.islformat before building the executable.new-language: Scaffolds a new language (e.g.,make new-language LANG=es_ES), creating a.pofile pre-filled with standard translations.translations-export-source: Updates the sourcemessages.pofrom the masteren.isl.translations-compile: Converts all localized.pofiles into.islfiles ready for packaging.References
Fixes #181
Translation Workflow
Prerequisites
make dependencies).make pyinstallerto build the application.pip install polib.1. Adding a New Language (Developer Task)
This process scaffolds the files for a new language using its ISO locale code.
Register the Language:
Open
installer/translations/definitions.pyand add the new ISO code to theLANG_DEFINITIONSdictionary. You must map it to the corresponding Inno Setup language name and Microsoft Language ID.This command runs the
create_new_language.pyscript, which createsinstaller/translations/Spanish.isl. The file will be pre-filled with standard Inno Setup translations if available, otherwise it will be a copy of the English template with empty values for translation.Scaffold the PO File:
Run the make target with the ISO code.
Spanish.isl).installer/translations/locale/es_ES/messages.po.Enable the Language in the Installer Script:
Open
installer/kolibri.issand add a new entry in the[Languages]section. Point to the.islfile (note: this file doesn't exist yet; it will be generated during the build).Commit Changes: Commit
definitions.py,kolibri.iss, and the newmessages.pofile.2. Syncing with Crowdin (Developer Task)
Update Source Strings:
If
en.islhas changed, regenerate the source PO file:Upload
installer/translations/locale/en/messages.poto Crowdin as the Source File.Upload Existing Translations:
If you just scaffolded a new language (Step 1) and it contains pre-filled standard translations, upload
installer/translations/locale/es_ES/messages.poto Crowdin as Existing Translations. This ensures translators don't re-do work Inno Setup has already done.3. Translating (Translator Task)
Translators work on Crowdin to translate the custom Kolibri strings (and any standard strings that were missing).
4. Integrating Translations (Developer Task)
Download Translations:
Download the completed
.pofile for the language from Crowdin.Update Repository:
Overwrite the local file at
installer/translations/locale/es_ES/messages.powith the version from Crowdin. Commit the change.5. Building the Installer (Developer Task)
The build process automatically converts the PO files into the ISL format required by Inno Setup.
Run the Build:
What Happens Behind the Scenes:
translations-compiletarget runs automatically.installer/translations/locale/.messages.pointo a compiled.islfile (e.g.,es_ES.isl).LanguageIDandLanguageNamefromdefinitions.pyinto the.islfile.Verify:
Run the generated installer in
dist-installer/. You should see the language selection dialog, and selecting the new language should switch the UI text accordingly.