[TIMOB-13966] TiAPI: Select Locale for L() at runtime
GitHub Issue | n/a |
---|---|
Type | New Feature |
Priority | High |
Status | Closed |
Resolution | Fixed |
Resolution Date | 2019-01-10T18:59:25.000+0000 |
Affected Version/s | Release 3.1.0 |
Fix Version/s | Release 8.0.0, Release 7.5.0, Release 7.5.1 |
Components | Core |
Labels | language, localization, runtime |
Reporter | Mads Moller |
Assignee | Joshua Quick |
Created | 2013-05-16T10:13:41.000+0000 |
Updated | 2021-03-15T18:43:30.000+0000 |
Description
*Problem*
It would be great if it was possible to let the user select which language to use at runtime. So instead the L() would only look at device language, it could also check if a Ti.App.LanguageXMLSetting property has been defined. That way we do not need to create homemade localization systems anymore.
The use case is included below. The problem is that L is undefined in style.tss because I redefine it in the controller.
*alloy.js*
// Language helper
Alloy.LanguageHelper = require('languageHelper');
Alloy.L = Alloy.LanguageHelper.fetchString;
*Controller*
//top of the controller
var L = require("languageHelper");
*Style*
"#menuBtn":{
title: L("MenuButton")
}
*View*
<Alloy>
<Window>
<Button id="menuBtn"></Button>
</Window>
</Alloy>
*Custom languageHelper*
/**
* Language Helper
* Makes it possible to change language at runtime
* @version 1.0
*/
var lookup = null;
exports.locale = Ti.App.Properties.getString('SETTING_LANGUAGE', Ti.Locale.getCurrentLanguage() );
/**
* Private function
* Loads the language xml file.
* It has fallback for english if file does not exist.
*/
function loadFile() {
lookup = {};
// LOAD FILE
var file = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'i18n/' + exports.locale + '/strings.xml');
if (!file.exists()) {
file = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'i18n/en/strings.xml');
if (!file.exists()) {
Ti.API.warn("Language file for both '" + exports.locale + "' and 'en' fallback do not exist");
return;
}
}
// PARSE XML
var xml_string = file.read().text;
var doc = Ti.XML.parseString(xml_string);
var strings = doc.getElementsByTagName("string");
for (var i = 0; i < strings.length; i++) {
var node = strings.item(i);
var value = node.text;
if (node.hasAttributes()) {
for (var att_index = 0; att_index < node.attributes.length; att_index++) {
var att = node.attributes.item(att_index);
if (att.nodeName === "name") {
lookup[att.nodeValue] = value;
}
}
}
}
}
/**
* Set the current language of the app.
* @param {String} language
*/
exports.setLanguage = function(language){
if(language === null){
return;
}
// clean old lookup
exports.clear();
// save language
Ti.App.Properties.setString('SETTING_LANGUAGE', language);
exports.locale = language;
// parse new lookup
loadFile();
return lookup;
}
/**
* Clear the parsed xml translations
*/
exports.clear = function() {
lookup = null;
};
/**
* Lookup function. require this at each controller
* Example usage:
* var L = require('languageHelper').fetchString;
* @param {Object} string
* @param {Object} hint
*/
exports.fetchString = function(string, hint) {
if (lookup === null) {
loadFile();
}
return lookup[string] !== undefined ? lookup[string].replace(/\\n/g, "\n") : (hint || string);
};
The big issue here is Alloy. I can write my own localization function, and use the i18n structure, if I place the folder inside Resources. But in Alloy, the controller code is compiled after the view/style. This means that I cannot have use L() in mycontroller.tss or markup. I will need to have all my text in the controller. This is bad design, and not really useful.
Can you include some code that exhibits the issue? Particularly can you show me what you are shopping now to work appc the issue, and what you would expect to be able to do when this issue is resolved. The more clear and concise in these samples toy can be, the more likely it is that they can be implemented sooner rather than later.
Hi Tony, This issue is not just an Alloy bug/feature. The best solution is the ability to change locale at runtime. If thats possible, then Alloy can just continue working as it does.
cheers, Mads
This should work out of the box using
This is an undocumented method that exists in Ti [here](https://github.com/appcelerator/titanium_mobile/blob/master/iphone/Classes/LocaleModule.m#L73) for iOS. On android seems [not to be supported](https://github.com/appcelerator/titanium_mobile/blob/master/android/modules/locale/src/java/ti/modules/titanium/locale/LocaleModule.java#L89) :( I works for me in [AsturPlaya](https://itunes.apple.com/es/app/asturplaya/id662587421?ls=1&mt=8) combined with [this module](https://github.com/rborn/tiapprestart) However this is a classic Ti app, not an Alloy one, and didn't test it yet. But because the app is restarted it should work with no issues.
Hurrah @Hans Knöchel. After 5 years its now in progress ;)
Ha, I know. It's even reported by you, funny. Let get this party started.
PR: https://github.com/appcelerator/titanium_mobile/pull/10227 Test-Setup (requires a
hello_world
language file in i18n/de and i18n/en):Some notes here: - It seems like (different to the initial discussion) the API [is documented](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Locale-method-setLanguage) by now - It seems like (different to the initial discussion) the API is also [supported on Android](https://github.com/appcelerator/titanium_mobile/blob/master/android/modules/locale/src/java/ti/modules/titanium/locale/LocaleModule.java#L95) already This PR will fix one remaining issue on iOS that prevents the locale to be forgotten across app-launches. I added a simple
UserDefaults
setting to hook into that behavior and clean it up. The only remaining thing I am wondering is if we should expose theTi.App.restart()
method as well, to restart the app for the user. This is pretty much against the native behavior and could lead in an app rejection, so I'd like to put down my vote against this for now. [~gmathews] [~jquick] Are we good on Android?Verified feature on SDK Version
7.5.0.v20181018133006
. Tested using the test case provided by [~hknoechel] on iOS. *Test Environment**Closing ticket* If any issue remains please reopen.
Doesn't seem to work correctly on *Android*, setting the textid directly does work. When you get a string with L('key') it doesn't work, what is strange is that it does seem to work when you run the app with liveview enabled. Test on: Samung s9 (Android 8.0.0) and emulator (Android 8.0.0). SDK: 7.5.0.GA Example (does need to have the strings.xml file):
Also doesn't work with me but it was working great with 7.4.1
Verified that it is an issue on
7.5.0.GA
on android, it has been fixed on master tested withSDK 8.0.0.v20181213061450
Will this be backported into 7.5.1?
[~vijaysingh] Can you please create a 7.5.1 backport?
Reopened to update fix version to include 7.5.1 backport
Did this backport has been merged yet?
Can someone clarify if locales are stable in 7.5.1 (both iOS and Android)?
I found that it was specific to the android version, results are below. || ||7.4.1.GA||7.5.0.GA||8.0.0.v20181217103214|| |4.4.2|(/)|(/)|(/)| |6.0|(/)|(/)|(/)| |8.1|(/)|(x)|(x)| |9.0|(/)|(x)|(x)|
This PR should fix this. https://github.com/appcelerator/titanium_mobile/pull/10549 Currently for master. We will discuss a backport in case it passes CR and FR.
Please note that changing language on Android broke in Titanium 7.5.0 due to an undocumented breaking-change on Google's end. It'll only happen if your app targets Android P/9 (aka: API Level 28). Yordan's above PR fixes it in Titanium 8.0.0. You can also work-around the issue by "targeting" API Level 27.
@Joshua, if we change the targetSDK version to 27 with SDK 7.5.x, will our app continue to work fine on Android OS 28 as well? I would like to stick to L('string') method so that when we get 8.x.x release, we won't need to change anything. so what is the best possible solution at present as we can't go back to any SDK than 7.5.x?
@Joshua, I just changed the Target-SDK with 7.5.1.RC build and it still doesn't work properly.
*FR Passed*. Waiting on Jenkins to merge.
merged to master/8_0_X
Re-opening... Yordan's PR does fix the issue with
L()
, but nowtitleid
andtextid
no longer read from the selected language/locale. And it happens on all Android OS versions when building with Titanium 8.0.0. (Building with 7.5.0 is fine.) _*Edit:*_ _I've isolated the issue. The problem is that the old code (before the last merged PR) did not load resources from the same context objects. TheL()
function loaded strings from the application context. The localized string ID properties load strings from the top-most or previous activity context. The issue here is that Android 8.0 and above's context objects no longer share the same resources/configuration objects. Best solution is to have them load resources from the same object for consistency. Yordan's last PR which updates the language/locale of the application context is the right move since backing out of the app and relaunch the main activity will preserve the last set language/locale setting._PR (master): https://github.com/appcelerator/titanium_mobile/pull/10591 PR (8.0.x): https://github.com/appcelerator/titanium_mobile/pull/10592
FR Passed. PR's merged.
Will this PR merge to 7.5.1?
[~ahmed.mohamed20320], no plans at the moment. Currently, all resources are on 8.0.0 to get a release candidate out in the near future. Stay tuned.
Ok, I will check it out, I ask because the fix version is for 8.0.0,7.5.0 and 7.51
[~ahmed.mohamed20320], This issue was resolved on iOS in 7.5.0. https://github.com/appcelerator/titanium_mobile/pull/10227 This issue will be resolved on Android in 8.0.0. https://github.com/appcelerator/titanium_mobile/pull/10549 https://github.com/appcelerator/titanium_mobile/pull/10592 We don't normally mix platforms and fixed releases like this in the same ticket. I apologize for the confusion. We should have split this into 2 separate tickets.
*Closing ticket.* Fixes from [PR/10227](https://github.com/appcelerator/titanium_mobile/pull/10227), [PR/10549](https://github.com/appcelerator/titanium_mobile/pull/10549) and [PR/10592](https://github.com/appcelerator/titanium_mobile/pull/10592) are all present in SDK {noformat} 8.0.0.v20190114020205 {noformat}
workaround in 7.5.0 https://github.com/genocsb/titanium_i18n
Quick question in the round: Does this work across app launches? Or does it have to be manually applied on each app start?
[~hknoechel], on Android, you have to call
setLanguage()
manually on every app startup or else it'll use the system's locale/language.