localization of assets files
AndroidLocalizationAndroid Problem Overview
I have several html files in assets folder. How can I localize them? Is my only option to put some hardcode to pick the right file based on locale?
Android Solutions
Solution 1 - Android
This isn't supported directly but here is what I have done...
Separate your files into groups by country code (like what you would do for normal resource files) and then create a localized string in each of your localized string.xml files called something like "prefix" (where prefix would be "en" for English for example).
Then when you build your asset filenames simple use something like getString("prefix") + "-" + "<name-of-asset->
.
At least some variation of the above should work for you.
Solution 2 - Android
Put your files into the assets folder with local suffix. Define a String resource 'myLocalizedFileName' for every file and get the filename via R.string.myLocalizedFileName.
Example:
Folder structure:
assets/
assets/help.html
assets/help_de.htlm
String resources for every language in res/values/strings.xml:
<resource>
<string name=helpFile>help.html</string>
</resource>
WebView calling:
public class HelpActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
...
findViewById(R.id.helpWebView)
.loadUrl("file:///android_asset/"
+ getString(R.string.helpFile));
}
}
Solution 3 - Android
If you want to localize a HTML file you can simply put it under res/raw-<language>/filename.html (where <language>=en, es, fr, it, etc.) then access it from your code with the resource ID R.raw.filename. The framework will pick the right file according to the locale.
Solution 4 - Android
An alternative to using one file per country code (as described in Andrew White's and PJ_Finnegan's answers) is to define the HTML only once (e.g. in the assets
folder) and use @string
IDs in it, like so
<html>
<body>
<p>@string/message_text</p>
</body>
</html>
After loading the asset into a string you could pass its content to replaceResourceStrings()
:
/**
* Regex that matches a resource string such as <code>@string/a-b_c1</code>.
*/
private static final String REGEX_RESOURCE_STRING = "@string/([A-Za-z0-9-_]*)";
/** Name of the resource type "string" as in <code>@string/...</code> */
private static final String DEF_TYPE_STRING = "string";
/**
* Recursively replaces resources such as <code>@string/abc</code> with
* their localized values from the app's resource strings (e.g.
* <code>strings.xml</code>) within a <code>source</code> string.
*
* Also works recursively, that is, when a resource contains another
* resource that contains another resource, etc.
*
* @param source
* @return <code>source</code> with replaced resources (if they exist)
*/
public static String replaceResourceStrings(Context context, String source) {
// Recursively resolve strings
Pattern p = Pattern.compile(REGEX_RESOURCE_STRING);
Matcher m = p.matcher(source);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String stringFromResources = getStringByName(context, m.group(1));
if (stringFromResources == null) {
Log.w(Constants.LOG,
"No String resource found for ID \"" + m.group(1)
+ "\" while inserting resources");
/*
* No need to try to load from defaults, android is trying that
* for us. If we're here, the resource does not exist. Just
* return its ID.
*/
stringFromResources = m.group(1);
}
m.appendReplacement(sb, // Recurse
replaceResourceStrings(context, stringFromResources));
}
m.appendTail(sb);
return sb.toString();
}
/**
* Returns the string value of a string resource (e.g. defined in
* <code>values.xml</code>).
*
* @param name
* @return the value of the string resource or <code>null</code> if no
* resource found for id
*/
public static String getStringByName(Context context, String name) {
int resourceId = getResourceId(context, DEF_TYPE_STRING, name);
if (resourceId != 0) {
return context.getString(resourceId);
} else {
return null;
}
}
/**
* Finds the numeric id of a string resource (e.g. defined in
* <code>values.xml</code>).
*
* @param defType
* Optional default resource type to find, if "type/" is not
* included in the name. Can be null to require an explicit type.
*
* @param name
* the name of the desired resource
* @return the associated resource identifier. Returns 0 if no such resource
* was found. (0 is not a valid resource ID.)
*/
private static int getResourceId(Context context, String defType,
String name) {
return context.getResources().getIdentifier(name, defType,
context.getPackageName());
}
The nice thing about this approach is that you have to specify the structure of the HTML only once and use android's localization mechanism. In addition, it allows recursively referencing strings in strings.xml, which is not supported by Context.getResources()
. For example:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="message_text">Some string @string/another_one.</string>
</resources>
The downside is that the parsing is done at runtime, so specifying a dedicated HTML for each language has a better performance when used within the app.
For an example that uses this code to convert HTML from an asset file to a "stylable" CharSequence
(using Kuitsi's TagHandler) that can be displayed in a TextView
see TextUtil
.
Solution 5 - Android
Putting files in raw-LOCALE folder will automatically pick the right location, but if you load those files in webView paths will be broken after obfuscation using Proguard.
https://stackoverflow.com/questions/12112102/proguard-breaks-android-webview-why
Then the only solution is to use assets folder and use file-LOCALE and pick up right files.
Solution 6 - Android
Trying to localize with assets-ja
will not work, because sadly these aren't resource files. The best option is to localize programmatically, with the right locale. Alternatively, the content of a HTML file is just plain text. If it fits with your project you can try storing this string as an entry in the strings.xml
(or your own myHtmlText.xml
?) file of a new values-ja
folder, for example.