My problem is all the texts comes from webservices. So, at the beginning, I call the webservice and I have to set the texts in strings.xml. I know writing in strings.xml it's impossible but we had the same problem on iOs and a solution has been found here http://iosapplove.com/archive/2013/01/localizable-strings-how-to-load-translations-dynamically-and-use-it-inside-your-iphone-app/.
So my question is : Is there a similar way on android ? (I think no)
It exists alternative way like using database or sharedPreferences but these solutions won't be very effective. Moreover, the application will contain many languages.
So my second question : What is the best way to do this ?
As you already know, the strings.xml files will not exist in the APK. This means that there is no way to change what getResources().getString(R.string.mystring) will return.
You say that "it's a request from customer". My suggestion is that you tell the customer "this is a bad idea". Because it really is.
If the customer persists I guess your only option is to download the texts in whatever language it is that you need and store it in your apps external file storage. The suggestion to use a properties file is good. Load it as an InputStream and get strings from it.
Have you considered what will happen if the app is in flight mode or does not have a network available when started? Will you provide default values?
Also, with this solution you will not be able to set any texts in TextViews or similar in XML since the system won't have access to your downloaded translations in the properties files.
If you still go ahead with this I'd also suggest that you wrap the Properties file into some other class and fall back to whatever texts you have in your strings.xml if a text doesn't exist in the properties file or if it wasn't downloaded for some reason:
class TextRetriever {
// the downloaded texts go here
private Properties properties;
private Context context;
public String getString(String key) {
String s = properties.getProperty(key);
// not found in properties file
if(s == null) {
// fall back to value in strings.xml
s = context.getResources().getIdentifier(key, "string", context.getPackageName())
}
return s;
}
}
Related
I am building an application and I am thinking of how to "parametrise" all the strings in the application (is it even possible) in order to allow me to change them easily without "redeploying" it again ...
meaning it will be somewhere in a file with strings (something like you have PO files in PHP when using templates and different languages) where I can manage it ..
it might be useful when I would like to use different languages :)
I am kinda struggling on this one, so I was thinking if you can give me a clue or show me where to "go" to study how this should be implemented ..
Thanks
If you want different langage in your app, create as many strings.xml files as you need. However, when you add new strings file, you have to redeploy.
To avoid that, you should call a specific API in backend which send you all the texts according to the langage of the device. For that you must manage back and front.
So if I understand your question correcly go to res - >value folder -> strings.xml - > open it and you will see something like this:
<string name="app_name">this is your app name</string>
And now every time that you want text to be "this is your app name" all you need to do is to add this line:
android:text="#string/app_name"
And when you will change the actual string in strings.xml it will also change in every place he is being used (android:text="#string/app_name")
Android has a built-in mechanism for localising assets (Strings included)
https://developer.android.com/guide/topics/resources/localization#creating-alternatives
What you are trying to do is known as 'Localisation', this can be helpful if you want to give users different language support and similar kind of stuff.
In android, this is done by putting all the strings in the Strings.xml file located in the res folder.
If you're using android studio, just press Alt + Enter on any hardcoded string and then select 'Extract string resource' from the popup, give the name of the string and Voila! you're done.
It is also a part of good coding practice, in fact you've might have noticed 'Lint warnings' in your layout files if you type any hardcoded string, and it asks you to add this string in the strings.xml file
You can create a separate String file for each language that you wanna include.
Option 1
Using the Default Built-In Mechanism
You already have answers about this or you can read the official documentation about it.
Essentially, you maintain an xml file called strings.xml which is a key-value of Strings. Each file (one per language) will be located in a values folder. E.g.: for french, you'd want values-fr/strings.xml. Each file will include all the translated strings. If a value is not found in French, then the english file will be searched instead as a fallback. If the key is not there, the app will crash (if I am not mistaken).
Pros
Included with the system
Supports many languages
Packed at compile time, always available.
Cons
Resources are "read-only" once they are compiled; you cannot change a string and save the change this way.
Option 2
Roll your own Thing.
If you need to be able to dynamically alter these strings, you'll need a few key pieces:
A way to obtain/download said strings.
A default in case step 1 fails (what if the user cannot download them?) You need defaults.
To ensure every widget that needs to display text, calls your own creation of a class that can manage said dynamic strings (i'll elaborate down below)
You need to know what to do if a String is somehow magically missing; because this is dynamic, there has to be a fallback in case the string is not found (see 2)
This has to be relatively fast (you don't want expensive lookups when constructing strings and UI elements).
Pros
You can model this the way it works best for you
You will be able to load strings as you see fit and even change them at runtime.
Cons
You have to build all this.
It's error prone, and most likely slower than the native solution.
You must ensure you don't miss strings and that you have dafults.
You must modify normal widgets (say TextView) to understand how to fetch the strings (or you must always provide them), and this is not going to be automatic, you'll either have to delegate or subclass into a YourCustomTextViewThatUnderstandsYourStringThing extends TextView (name... is just a draft ;) )
You must ensure you don't leak memory by keeping strings in memory you don't care anymore.
If you want to "persist" these downloaded languages (and you should), you have to write/use your own persisting mechanism (either by writing the files or by using some database, shared preferences is not the place for these).
You need to cache them (see above) and manage the validity of the strings (what if they become old, can they become old? when should you re-fetch them?)
etc.
As you can see it's not trivial and each app has its own world of problems to solve, but that's roughly what it means.
As for "code"... the simplest way I can think of (or rather, the bare basics) are:
Find a way to "store" the strings: e.g.:
Map<String, String> oneLanguage
So in this Map, you store the KEY (to find the value) and the VALUE:
oneLanguage.put("app_name", "My Super App")
Keep all the strings in one place in memory:
Map<String, Map<String, String>> allLanguages
so then you do:
allLanguages.put("English", oneLanguage);
Now you can add other languages:
anotherLanguage.put("app_name", "Mi Super App"); //my Spanish is top-notch
allLanguages.put("Spanish", anotherLanguage);
Now you have a place where to store (in memory) all your keys/values for each language.
When you want to retrieve Spanish, you'd have a method like:
String getString(#NonNull String locale, #NonNull String key) {
return allLanguages.get(locale).get(key);
}
And then you'd need to (either manually or via subclassing or whatever approach you find more convenient) to set these strings like:
// I mean, don't hardcode the locale... but you get the idea.
appNameTextView.setText(yourLanguageManager.getString("Spanish", "app_name"));
What approach you take for this last step, is entirely up to you.
As usual, all the above is pseudo-code, and not a complete solution to this approach. Things you want to do: ask the device what locale is using, keep track of which locale is in use (so you don't have to pass it every time), fetch these locales from your server (not pictured) :), persist these to disk, as well as save in shared preferences, the "locale" key that the user has selected, add methods to your yourLanguageManager so it can have things like:
boolean updateLocale(String locale, Map<String, String newStrings)
boolean deleteLocale(String locale)
Map<String, String> getLocale(String locale)
etc.. you get the idea.
All in all, it's just a simple data structure that can contain keys and values.
Good luck!
My app uses a lot of static text and I'm trying to find an optimal way to persist and display that text. For now, I don't need to focus on localizing the text so, all the text goes into the strings.xml and that presents a lot of formatting nightmares.
Of course, it is not 100% static content, I sometimes have dynamic values in there which in my case can stay within strings.xml so, what is the right way for persisting this static text?
Static text content is exactly what you want to use strings.xml for, and you automatically get the added bonus of easier localization as you can have different strings.xml for the different languages. No code changes required, just different XML files.
Dynamic content is going to be content which changes based on user input. You can still use strings.xml to store the static portion (if any) of that dynamic content. Like the "format" string you may pass to String.format() or something similar.
Use the resources support for this, it is exactly what it was intended to do (and do it efficiently.)
Your comment is contradictory; you say:
For now, I don't need to focus on localizing the text so, all the text goes into the strings.xml
if you don't need Localizing, then why are you using strings.xml?
Of course, the answer is because regardless of localizing, strings.xml is the perfect place for this.
I don't know what kind of nightmares you have with it, but it's not different from any other string:
E.g. of a strings.xml:
<string name="refresh">Refresh</string>
<string name="order_placed" formatted="false">Order Placed: %s</string>
You can later use the same formatter for them:
getString(R.string.order_placed, "3pm")
will output:
Order Placed: 3pm
If you need new lines…
<string name="error">Something bad happened.\nPlease try again.</string>
will output:
Something bad happened.
Please try again.
And so forth.
Additionally, if you have trouble naming your resources, I've been following more or less this idea and despite the shortcomings described at the bottom, they haven't been a big deal with Android Studio fancy refactoring tools.
You could create a class called StaticBuffer or something with a static String array as a data member.
class StaticBuffer
{
static String array[];
}
Then you could initialize it in your onCreate() or any other function and use it.As it is static it's values will reflect the changes that you make everywhere.
Eg :
//Initialization
StaticBuffer.array=new String[10];
//Usage
StaticBuffer.array[0]="Item1";
PS: I got this idea from a friend of mine. :)
I have a simple question (may be very dumb), however I did not find an answer goggling around.
I'm trying to save a simple preference say "high score" on my game that I'm using libgdx to build.
Here is my sample code -
Preferences prefs2;
prefs2= Gdx.app.getPreferences("MyPreferences");
prefs2.putString("name", "Donald Duck");
String name = prefs2.getString("name", "No name stored");
font.draw(textBatcher, name, 55, 55);
Everything in the code (reading and writing to the xml file MyPreferences) works as expected except one thing; the high score stored on the file doesn't work when I restart the game. I definitely know that I'm missing something that is very simple but don't know what it is :)
Can any one please help me ?
I have also tried this -
Preferences prefs2;
if (prefs2 == null){
prefs2= Gdx.app.getPreferences("MyPreferences");
}
However it doesn't seem to work.
You're lacking a call to prefs2.flush() after you added the highscore item to the preferences object.
Straight from the libgdx docs:
http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/Preferences.html#flush()
i have a query regarding location files. i want to download localization files from a server (string.xml, string_ar.xml , etc) on the launch of the android application instead of declaring previously. is there any way to achieve this task..
Thanks in advance
If you want to know that the app is running for the first time, you could save a boolean value in the preferences. Something like first_run.
If you download localization files you will not be able to use the default localization support.
(Not sure why you wouldn't just place them in the apk)
What you could do is getting the localization and hitting the server for the correct translations.
You could create your own class that takes care of getting strings and if they do not exist, fail over the android one.
Something like:
public static String getString(Context ctx, int key) {
String ret = getStringFromDB(key);
if ( ret == null ) {
ret = ctx.getString(key);
}
return ret;
}
In android we have files by name String. Developers define the string values that they used for naming objects in this file. This is a very useful way. Because avoid of hard coding string values(you can change them from a single file, less time to change), also useful to creating multi language application and etc. (for more info just google this).
But the question is this: whether iPhone(Monotouch) have a mechanism like this to define strings on them or developers have to define themselves mechanism for this?
In XCode, you'll find File/New File, then on the left, pick "Resource", and you'll find "Strings File".
From code, you'll be referencing the keys in your strings file with NSLocalizedString:
NSLog("%#", NSLocalizedString(#"YOUR-STRING-KEY-OR-DEFAULT-VALUE", #"Comment about what this is"));
For details on what that second param is for, What is the second parameter of NSLocalizedString()?
Put your strings in a single file. Make them global constants. Access them throughout the app. When you change these, the change will be reflected everywhere.
It's not a big deal to have persistent string references throughout your app. It can be done in any decent programming language and platform I suppose.