使用NSLocalizedString的最佳实践


140

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

不幸的是,存在一些“缺点”(不一定是NSLocalizedString本身的错误),包括

  • Xcode中的字符串没有自动完成功能。这使工作不仅容易出错,而且很麻烦。
  • 您可能仅仅因为不知道已经存在等效字符串而重新定义了一个字符串(例如,“请输入密码”与“首先输入密码”)
  • 与自动完成问题类似,您需要“记住” /复制粘贴注释字符串,否则genstring最终将对一个字符串产生多个注释
  • 如果要genstring在对某些字符串进行本地化之后使用,则必须小心不要丢失旧的本地化版本。
  • 相同的字符串分散在整个项目中。例如,您NSLocalizedString(@"Abort", @"Cancel action")在各处使用过代码,然后Code Review要求您重命名字符串NSLocalizedString(@"Cancel", @"Cancel action")以使代码更加一致。

我要做的(在SO上进行了一些搜索之后,我发现很多人都这样做了)是要有一个单独的strings.h文件,其中存放#define所有的本地化代码。例如

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

这实质上提供了代码补全,更改变量名称的单个位置(因此不再需要genstring)和用于自动重构的唯一关键字。但是,这样做的代价是最终要获得一堆#define不是固有结构化的语句(例如LocString.Common.Cancel或类似的东西)。

因此,尽管这工作得很好,但我想知道你们在项目中如何做到这一点。还有其他方法可以简化NSLocalizedString的使用吗?甚至有一个封装它的框架吗?


我就像你一样做。但是我正在使用NSLocalizedStringWithDefaultValue makro为不同的本地化问题(如控制器,模型等)创建不同的字符串文件,并创建初始默认值。
anka 2012年

似乎xcode6的Export to localization不能捕获在头文件中定义为宏的字符串。谁能确认或告诉我我可能会想念什么?谢谢...!
Juddster 2014年

@Juddster,可以确认,即使有新基金编辑器- >导出本地化它不会在头文件回升

Answers:


100

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本地化方面提高工作效率!


感谢您的回答,我一定会看一下您的python文件。我同意您的命名约定。我最近与其他一些iOS开发人员进行了交谈,他们建议使用静态字符串而不是宏,这很有意义。我已对您的答案进行了投票,但在接受之前会稍等,因为解决方案仍然有些笨拙。也许会有更好的结果。再次感谢!
JiaYow 2012年

不客气 本地化是一个繁琐的过程,拥有正确的工具和工作流程将与众不同。
ndfred 2012年

17
我从来没有理解过为什么gettext样式的本地化功能使用一种翻译作为键。如果您的原始文本更改会怎样?您的密钥已更改,所有本地化文件的密钥均使用旧文本。这对我从来没有任何意义。我一直使用“ home_button_text”之类的键,因此它们是唯一的,并且永不更改。我还编写了一个bash脚本来解析我所有的Localizable.strings文件,并生成带有静态方法的类文件,该方法将加载适当的字符串。这使我完成代码。有一天我可能会开源。
Mike Weller 2012年

2
我想你genstrings不是说gestring
hiroshi

1
@ndfred编译时检查您没有输入错误的字符串是最大的胜利。无论如何,要增加一点代码。同样在重构,静态分析的情况下,使用符号会使事情变得更加容易。
艾伦·曾


24

同意ndfred,但我想补充一点:

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

(NSLocalizedStringWithDefaultValue不能与genstring一起正常使用,这就是我提出此解决方案的原因)

这是我的使用NSLocalizedString的Custom实现,该NSLocalizedString使用comment作为默认值:

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.使用它!

确保在应用程序构建阶段中添加运行脚本,以便在每次构建时都会更新Localizable.strings文件,即,将在Localized.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”

Voilà:-)


正在获取ARC错误,没有已知的选择器'localizedString:comment ::((
Mangesh

我想是因为缺少LocalizationHandlerUtil.h。我找不到代码...只需尝试创建头文件LocalizationHandlerUtil.h,它就可以了
Pascal

我已经创建了文件。我认为这是由于文件夹路径问题引起的。
Mangesh 2014年

3

在Swift中,我使用以下方法,例如,在这种情况下用于按钮“是”:

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

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

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

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

我希望其中一些人有用。


2

如果有人在寻找Swift解决方案。您可能想看看我在这里整理的解决方案:SwiftyLocalization

只需设置几个步骤,您就可以在Google Spreadsheet中实现非常灵活的本地化(注释,自定义颜色,突出显示,字体,多张工作表等)。

简而言之,步骤如下:Google Spreadsheet-> CSV文件-> Localizable.strings

此外,它还会生成Localizables.swift,该结构的作用类似于为您检索和解码键的接口(不过,您必须手动指定从键解码String的方法)。

为什么这么好?

  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脚本转换工作表-> CSV,并使用Python脚本转换工作表-> Localizable.strings您可以快速浏览此示例工作表以了解可能的情况。


1

在iOS 7和Xcode 5中,应避免使用“ Localization.strings”方法,而应使用新的“基本本地化”方法。如果您用Google搜索“基本本地化”,则周围有一些教程

Apple doc:基本本地化


是的,史蒂夫是正确的。此外,对于任何动态生成的字符串,您仍然需要.strings文件方法。但是只有这些,Apple的首选方法是基础本地化。
Ronny Webers 2014年

链接到新方法?
Hyperbole 2014年

1
Imo的基本本地化方法毫无价值。您仍然必须保留其他位置文件来存储动态字符串,并且它可以使您的字符串分散到很多文件中。Nibs / Storyboards内部的字符串可以使用某些库(例如github.com/AliSoftware/OHAutoNIBi18n
Rafael Nobre

0
#define PBLocalizedString(key, val) \

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

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"

当然可以更完善,但我想分享。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.