使用NSLocalizedString的最佳实践

146

我(和其他所有人一样)使用NSLocalizedString来本地化我的应用程序。

不幸的是,有几个“缺点”(不一定是NSLocalizedString本身的问题),包括:

  • Xcode中没有字符串自动完成。这不仅让工作容易出错,还很繁琐。
  • 您可能会重新定义一个字符串,只是因为您不知道已经存在相应的字符串(例如,“请输入密码”与“先输入密码”)
  • 类似于自动完成问题,您需要“记住”/复制粘贴注释字符串,否则genstring将会在一个字符串上有多个注释。
  • 如果您想在本地化一些字符串之后使用genstring,则必须小心,以免丢失旧的本地化。
  • 相同的字符串分散在整个项目中。例如,您在各处都使用了NSLocalizedString(@“Abort”,@“取消操作”),然后代码审查要求您将该字符串重命名为NSLocalizedString(@“Cancel”,@“取消操作”)以使代码更一致。

我所做的事情(经过对stackoverflow的一些搜索,我发现许多人都这样做)是拥有一个单独的strings.h文件,在那里我#define了所有的本地化代码。例如:

// In strings.h
#define NSLS_COMMON_CANCEL NSLocalizedString(@"Cancel", nil)
// Somewhere else
NSLog(@"%@", NSLS_COMMON_CANCEL);

这实际上提供了代码补全的功能,一个单一的地方来更改变量名(因此不再需要使用genstring),并提供了一个独特的关键字来自动重构。然而,这样做的代价是最终会得到许多本质上不结构化(例如像LocString.Common.Cancel)的#define语句。

因此,虽然这个方法有些可以,但我想知道你们在项目中是如何做的。是否有其他方法来简化NSLocalizedString的使用?甚至可能有一个封装它的框架吗?


我基本上和你做的一样。但我使用 NSLocalizedStringWithDefaultValue 宏来为不同的本地化问题(如控制器、模型等)创建不同的字符串文件,并创建一个初始默认值。 - anka
似乎xcode6的本地化导出功能无法捕获在头文件中定义为宏的字符串。有人能否确认或告诉我可能遗漏了什么?谢谢! - Juddster
@Juddster,可以确认,即使使用新的Fund Editor->Export for Localization,它也不会在头文件中被捕获。 - Red
9个回答

101

NSLocalizedString 有一些限制,但它在Cocoa中非常核心,因此编写自定义代码来处理本地化是不合理的,这意味着您必须使用它。话虽如此,一些工具可以帮助,下面是我的做法:

更新字符串文件

genstrings 会覆盖您的字符串文件,丢弃所有先前的翻译。我编写了update_strings.py 来解析旧的字符串文件,运行genstrings并填充空白,以便您不必手动还原现有的翻译。该脚本尝试尽可能精细地匹配现有字符串文件,以避免更新时出现太大的差异。

命名您的字符串

如果按照宣传使用NSLocalizedString

NSLocalizedString(@"Cancel or continue?", @"Cancel notice message when a download takes too long to proceed");

你可能会在代码的另一个部分定义相同的字符串,这可能会导致冲突,因为同一个英文术语在不同的上下文中可能具有不同的含义(例如OKCancel)。

这就是为什么我总是使用一个带有模块特定前缀和非常精确描述的无意义全大写字符串:

NSLocalizedString(@"DOWNLOAD_CANCEL_OR_CONTINUE", @"Cancel notice window title when a download takes too long to proceed");

在不同地方使用相同的字符串

如果您需要多次使用同一字符串,可以像之前使用宏那样,也可以将其缓存为视图控制器或数据源中的实例变量。这样,您不必重复描述,避免了同一本地化实例中的描述内容过时和不一致,这总是让人感到困惑。

由于实例变量是符号,因此您可以在最常见的翻译上使用自动补全,并为特定的翻译使用“手动”字符串,这些特定翻译通常只会出现一次。

希望这些提示能让你在Cocoa本地化方面更加高效!


19
我从未理解gettext风格的本地化函数为什么使用其中一个翻译作为关键字。如果原始文本更改会发生什么?您的关键字将更改,所有本地化文件都将使用旧文本作为其关键字。我一直使用像“home_button_text”这样的关键字,以便它们是唯一的且永远不会更改。我还编写了一个bash脚本来解析所有我的Localizable.strings文件并生成一个带有静态方法的类文件,该方法将加载适当的字符串。这使我能得到代码完成提示。有一天我可能会开源这个。 - Mike Weller
为什么使用静态字符串而不是宏? - Elliot
2
我认为你的意思是 genstrings 而不是 gestring - hiroshi
@Mike Weller:使用源语言的本地化文本作为键的一个优点是,没有改变源文本并忘记更新翻译的风险。缺点是您失去了旧翻译和新键之间的链接,因此您会使现在(通常只是略微)过时的翻译变得孤立(更新这些翻译比从头开始编写要少一些工作)。但是,这可以通过工具轻松解决-查找与缺少翻译的键相差很小的孤立翻译。 - Martin Gjaldbaek
1
@ndfred 编译时检查你是否输入了错误的字符串是最大的优势。添加这个功能需要稍微多写一点代码,但在重构和静态分析方面,拥有一个符号将使事情变得更加容易。 - Allen Zeng
显示剩余6条评论

31

3
这真的很神奇。无需创建宏。 - Beau Nouvelle
1
页面未找到。 - Juanmi
1
@Juanmi 感谢您提到那个失效的链接,我已经使用 Github 的 URL 替换了链接。 - hiroshi

25

我同意ndfred的看法,但我想补充一下:

第二个参数可以用作...默认值!!

(NSLocalizedStringWithDefaultValue与genstring不兼容,这就是为什么我提出了这个解决方案)

以下是我的自定义实现,它使用NSLocalizedString,并将注释用作默认值:

1. 在您的预编译头文件(.pch文件)中,重新定义“NSLocalizedString”宏:

// cutom NSLocalizedString that use macro comment as default value
#import "LocalizationHandlerUtil.h"

#undef NSLocalizedString
#define NSLocalizedString(key,_comment) [[LocalizationHandlerUtil singleton] localizedString:key  comment:_comment]

2. 创建一个类来实现本地化处理程序

#import "LocalizationHandlerUtil.h"

@implementation LocalizationHandlerUtil

static LocalizationHandlerUtil * singleton = nil;

+ (LocalizationHandlerUtil *)singleton
{
    return singleton;
}

__attribute__((constructor))
static void staticInit_singleton()
{
    singleton = [[LocalizationHandlerUtil alloc] init];
}

- (NSString *)localizedString:(NSString *)key comment:(NSString *)comment
{
    // default localized string loading
    NSString * localizedString = [[NSBundle mainBundle] localizedStringForKey:key value:key table:nil];

    // if (value == key) and comment is not nil -> returns comment
    if([localizedString isEqualToString:key] && comment !=nil)
        return comment;

    return localizedString;
}

@end

3. 使用它!

确保在您的应用程序构建阶段中添加一个“Run script”,以便在每次构建时更新您的Localizable.strings文件,即新的本地化字符串将添加到您的Localizable.strings文件中:

我的构建阶段脚本是一个shell脚本:

Shell: /bin/sh
Shell script content: find . -name \*.m | xargs genstrings -o MyClassesFolder

因此,当你在代码中添加这一行时:

self.title = NSLocalizedString(@"view_settings_title", @"Settings");

然后执行构建操作,你的./Localizable.scripts文件将包含这一新行:

/* Settings */
"view_settings_title" = "view_settings_title";

因为'view_settings_title'的键和值相同,所以自定义的LocalizedStringHandler将返回注释,即“Settings”。

嗯,就这样 :-)


出现ARC错误,没有已知的实例方法选择器'localizedString:comment::(。 - Mangesh
我猜是因为缺少LocalizationHandlerUtil.h文件。我找不到那段代码了...只需要尝试创建头文件LocalizationHandlerUtil.h,问题就会解决。 - Pascal
我已经创建了这些文件。我认为这是由于文件夹路径问题所致。 - Mangesh

4
在Swift中,我使用以下代码来实现按钮"Yes"的功能,例如:
NSLocalizedString("btn_yes", value: "Yes", comment: "Yes button")

请注意使用value:来指定默认文本值。第一个参数用作翻译ID。使用value:参数的优点在于可以随后更改默认文本,而翻译ID保持不变。Localizable.strings文件将包含"btn_yes" = "Yes"; 如果未使用value:参数,则第一个参数将用于翻译ID和默认文本值。Localizable.strings文件将包含"Yes" = "Yes";。这种管理本地化文件的方式似乎有些奇怪。特别是如果翻译文本很长,那么ID也会很长。每当更改默认文本值的任何字符时,翻译ID也会更改。这会导致在使用外部翻译系统时出现问题。更改翻译ID被理解为添加新的翻译文本,这可能并不总是期望的。

2
如果有人正在寻找一种快速的解决方案,您可以查看我在这里提供的解决方案:SwiftyLocalization。
只需几个简单的步骤设置,您就可以在Google电子表格中实现非常灵活的本地化(包括注释、自定义颜色、高亮、字体、多个工作表等)。
简而言之,步骤如下:Google电子表格--> CSV文件-->Localizable.strings。
此外,它还生成了Localizables.swift,这是一个结构体,类似于接口,用于检索和解码键(但您必须手动指定从键解码字符串的方式)。
为什么这很棒呢?
1. 您不再需要将密钥作为纯字符串放置在各个位置。 2. 在编译时检测到错误的键。 3. Xcode可以进行自动完成。
虽然有工具可以自动完成您的本地化键,但引用实际变量将确保它始终是有效的键,否则它将无法编译。
// It's defined as computed static var, so it's up-to-date every time you call. 
// You can also have your custom retrieval method there.

button.setTitle(Localizables.login.button_title_login, forState: .Normal)

这个项目使用Google App Script将Sheets表格转换为CSV文件,使用Python脚本将CSV文件转换为Localizable.strings文件。你可以查看这个示例表格以了解可能的情况。


2

我编写了一个脚本来帮助维护多种语言的Localizable.strings文件。虽然它不能帮助自动完成,但它可以使用以下命令合并.strings文件:

merge_strings.rb ja.lproj/Localizable.strings en.lproj/Localizable.strings

更多信息请参见: https://github.com/hiroshi/merge_strings

希望这对你们有用。


1

在iOS 7和Xcode 5中,应避免使用“Localization.strings”方法,而应使用新的“基础本地化”方法。如果您搜索“base localization”,则会出现一些教程。

苹果文档:基础本地化


是的,Steve说得对。此外,对于任何动态生成的字符串,您仍需要.strings文件方法。但是仅针对这些,苹果首选的方法是基础本地化。 - Ronny Webers
新方法的链接呢? - Hyperbole
1
在我看来,基本的本地化方法是毫无价值的。你仍然需要为动态字符串保留其他位置文件,并且它会使你的字符串分散在许多文件中。使用一些库(例如https://github.com/AliSoftware/OHAutoNIBi18n),可以将Nibs/Storyboards内部的字符串自动本地化为Localizable.strings中的键。 - Rafael Nobre

0

我经常沉迷于编程,忘记把条目放入.strings文件中。因此,我有辅助脚本来查找我还欠哪些内容需要放回.strings文件并进行翻译。

由于我在 NSLocalizedString 上使用自己的宏,请在使用之前 检查和更新脚本,因为我假设简单地将 nil 用作 NSLocalizedString 的第二个参数。您需要更改的部分是

NSLocalizedString\(@(".*?")\s*,\s*nil\) 

只需将其替换为与您的宏和 NSLocalizedString 使用匹配的内容。

这里是脚本,实际上只需要第3部分。其余部分是为了更容易地查看它来自何处:

// Part 1. Get keys from one of the Localizable.strings
perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings

// Part 2. Get keys from the source code
grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/'

// Part 3. Get Part 1 and 2 together.

comm -2 -3 <(grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/' | sort | uniq) <(perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings | sort) | uniq >> fr-localization-delta.txt

输出文件包含在代码中找到但未在Localizable.strings文件中找到的键。以下是一个示例:

"MPH"
"Map Direction"
"Max duration of a detailed recording, hours"
"Moving ..."
"My Track"
"New Trip"

当然可以再进行一些改进,但我想分享一下。


0
#define PBLocalizedString(key, val) \

[[NSBundle mainBundle] localizedStringForKey:(key) value:(val) table:nil]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接