localization of assets files

AndroidLocalization

Android 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmishkinView Question on Stackoverflow
Solution 1 - AndroidAndrew WhiteView Answer on Stackoverflow
Solution 2 - AndroidChristian SchulzendorffView Answer on Stackoverflow
Solution 3 - AndroidPJ_FinneganView Answer on Stackoverflow
Solution 4 - AndroidschnattererView Answer on Stackoverflow
Solution 5 - AndroidjogoView Answer on Stackoverflow
Solution 6 - AndroidSK9View Answer on Stackoverflow