Android String Placeholders
2015-12-01 15:04
435 查看
http://www.piwai.info/android-string-placeholders/
This article reviews different ways to create dynamic translatable strings in Android.
In Android, message strings are extracted to XML files, and the system loads the resources corresponding to the current configuration.
res/values/strings.xml
res/values-fr/strings.xml
Let's say we want to display a dynamic string, such as Player Foo - Score: 42.
We may be tempted to implement that quickly with
Wrong
You will get a compile time error message on the
Multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute?
This error message is misleading, because one may believe that using
the way to go.
Still Wrong
Although the error message now disappears, the real solution is to use a positional format.
Right
When translating strings, the word order will change. For instance, Name: John Smith in English becomes Nom : Smith John in French.
When translating strings, the word order will change. For instance, Name: John Smith in English becomes Nom : Smith John in French.
res/values/strings.xml
res/values-fr/strings.xml
Using positional format prevents translation mistakes.
Did you know that instead of
an overloaded version of
Is that stricly equivalent to the previous code? Let's look at the Android source!
Almost the same, except that we are using the resource configuration locale, whereas we were previously using the default locale.
so this won't really be a problem until you start messing with the default locale.
By the way, you probably know that
available on
What's the difference? None. It just delegates to
If someone knows the story behind this weird shortcut method, let me know. For now, I'll just assume this is a consequence of Drunk Driven Development.
Your users deserve better than Google translate. XML resource files should be translated by a professional translator.
This translator will know nothing about your app internals. Therefore, it may be really hard to find out what those
signs mean.
You can use comments to help the translator.
By the way, if you need excellent quality software translation, I know someone that's been translating software for more than 25 years. Yes, he is my father :) .
Another interesting approach is to use named placeholders instead of format specifiers.
I won't discuss which syntax is better for this kind of problem, let's just pick a simple one:
I find this much more readable! Now, you'll need an API to transform that format to the final string.
Implementing this API is fairly straightforward.
This is just an example implementation, I'll leave a better one to you as an exercice. An interesting point here is that
so you can use it as a fluid API.
I shamelessly stole this
Burke, and thought it was worth sharing.
Of course, you may already use Java libraries that can do this. If you are aware of a good one that does a decent job, let me know!
Android String Placeholders
This article reviews different ways to create dynamic translatable strings in Android.
Quick reminder
In Android, message strings are extracted to XML files, and the system loads the resources corresponding to the current configuration.res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="sexy_button_title">Click me, I'm famous!</string> </resources>
res/values-fr/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="sexy_button_title">Cliquez-moi, parce que je le vaux bien !</string> </resources>
Resources resources = context.getResources(); String sexyButtonTitle = resources.getString(R.string.sexy_button_title);
Formatting strings
Let's say we want to display a dynamic string, such as Player Foo - Score: 42.We may be tempted to implement that quickly with
String.format().
Wrong
<string name="score_format">Player %s - Score: %d</string>
Resources resources = context.getResources(); String scoreString = String.format(resources.getString(R.string.score_format), player, score);
You will get a compile time error message on the
<string />definition.
Error
Multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute?This error message is misleading, because one may believe that using
formatted="false"is
the way to go.
Still Wrong
<string name="score_format" formatted="false">Player %s - Score: %d</string>
Although the error message now disappears, the real solution is to use a positional format.
Right
<string name="score_format">Player %1$s - Score: %2$d</string>
When translating strings, the word order will change. For instance, Name: John Smith in English becomes Nom : Smith John in French.
When translating strings, the word order will change. For instance, Name: John Smith in English becomes Nom : Smith John in French.
Resources resources = context.getResources(); String scoreString = String.format(resources.getString(R.string.name), firstname, lastname);
res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="name">Name: %1$s %2$s</string> </resources>
res/values-fr/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="name">Nom : %2$s %1$s</string> </resources>
Using positional format prevents translation mistakes.
getString()
Did you know that instead of String.format(), you can use
an overloaded version of
getString()that handles formatting?
Resources resources = context.getResources(); String scoreString = resources.getString(R.string.score_format, player, score);
Is that stricly equivalent to the previous code? Let's look at the Android source!
public class Resources { // … public String getString(int id, Object... formatArgs) throws NotFoundException { String raw = getString(id); return String.format(mConfiguration.locale, raw, formatArgs); } }
Almost the same, except that we are using the resource configuration locale, whereas we were previously using the default locale.
public final class String implements Serializable, Comparable<String>, CharSequence { // … public static String format(String format, Object... args) { return format(Locale.getDefault(), format, args); } }
Locale.getDefault()is usually equal to
mConfiguration.locale,
so this won't really be a problem until you start messing with the default locale.
By the way, you probably know that
getString()is also
available on
Context.
String scoreString = context.getString(R.string.score_format, player, score);
What's the difference? None. It just delegates to
Resources.
public abstract class Context { // … public final String getString(int resId, Object... formatArgs) { return getResources().getString(resId, formatArgs); } }
If someone knows the story behind this weird shortcut method, let me know. For now, I'll just assume this is a consequence of Drunk Driven Development.
Professional Translation
Your users deserve better than Google translate. XML resource files should be translated by a professional translator.This translator will know nothing about your app internals. Therefore, it may be really hard to find out what those
%1$scryptic
signs mean.
<string name="score_format">Player %1$s - Score: %2$d</string>
You can use comments to help the translator.
<!-- %1$s is the player nickname and %2$d is the player score -->
<string name="score_format">Player %1$s - Score: %2$d</string>
By the way, if you need excellent quality software translation, I know someone that's been translating software for more than 25 years. Yes, he is my father :) .
Using placeholders
Another interesting approach is to use named placeholders instead of format specifiers.I won't discuss which syntax is better for this kind of problem, let's just pick a simple one:
{placeholder}.
<string name="score_format">Player {nickname} - Score: {score}</string>
I find this much more readable! Now, you'll need an API to transform that format to the final string.
TagFormat scoreFormat = TagFormat.from(getString(R.string.score_format)); scoreFormat.with("nickname", player); scoreFormat.with("score", score); String scoreString = scoreFormat.format();
Implementing this API is fairly straightforward.
public class TagFormat { public static TagFormat from(String format) { return new TagFormat(format); } private final String format; private final Map<String, Object> tags = new LinkedHashMap<String, Object>(); private TagFormat(String format) { this.format = format; } public TagFormat with(String key, Object value) { tags.put("\\{" + key + "\\}", value); return this; } public String format() { String formatted = format; for (Entry<String, Object> tag : tags.entrySet()) { // bottleneck, creating temporary String objects! formatted = formatted.replaceAll(tag.getKey(), tag.getValue().toString()); } return formatted; } }
This is just an example implementation, I'll leave a better one to you as an exercice. An interesting point here is that
with()returns
this,
so you can use it as a fluid API.
String scoreString = TagFormat.from(getString(R.string.score_format)) .with("nickname", player) .with("score", score) .format();
Conclusion
I shamelessly stole this {placeholder}idea from Eric
Burke, and thought it was worth sharing.
Of course, you may already use Java libraries that can do this. If you are aware of a good one that does a decent job, let me know!
相关文章推荐
- Android支付之支付宝支付(一)
- Android 5.1 Dialog 溢出
- AndroidManifest.xml 配置文件
- 安卓扫码:简单的ZXing使用记录
- Android开发中Socket通信的基本实现方法讲解
- Android编程实现设置按钮背景透明与半透明及图片背景透明的方法
- android图表收益曲线-MPAndroidChart
- Android快捷方式解密
- Android编程实现TextView字体颜色设置的方法小结
- Android获取图片任意一点的RGB值
- Android 使用SharedPreferences 进行保存账号or密码or其它数据
- Android 料阅读及视频观看,先下载后打开
- mac 安卓调试 找不到手机
- Android代码混淆之混淆规则
- 29个android开发常用的类、方法及接口
- Android Frame动画
- Android Studio 快捷键
- android 自定义跑马灯
- Android编程实现屏幕自适应方向尺寸与分辨率的方法
- Android MotionEvent中getX()和getRawX()的区别