使用 NSLocalizedString 的最佳实践

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

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

  • 在 Xcode 没有字符串的自动补全,这使得工作不仅容易出错,而且令人厌烦。
  • 您最终可能会重新定义一个字符串,仅仅因为您不知道已经存在一个等价的字符串(即“请输入密码”与“先输入密码”)
  • 与自动完成问题类似,您需要“记住”/复制粘贴注释字符串,否则 genstring将为一个字符串提供多个注释
  • 如果您想在已经对某些字符串进行了本地化之后再使用 genstring,则必须小心不要丢失旧的本地化。
  • 相同的字符串分散在整个项目中。例如,您在任何地方都使用 NSLocalizedString(@"Abort", @"Cancel action"),然后 CodeReview 要求您将字符串重命名为 NSLocalizedString(@"Cancel", @"Cancel action"),以使代码更加一致。

我所做的(经过一些搜索,我认为很多人都这样做)是有一个单独的 strings.h文件,其中我 #define所有的本地化代码。比如说

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

这实际上提供了代码完成、一个更改变量名的位置(因此不再需要 genstring)和一个用于自动重构的惟一关键字。然而,这样做的代价是最终得到一大堆本身没有结构化的 #define语句(比如 LocString)。常见。取消或类似的东西)。

所以,虽然这个工作有点好,我想知道你们是如何做到这一点,在你们的项目。是否有其他方法来简化 NSLocalizedString 的使用?有没有一个框架来封装它?

76422 次浏览

NSLocalizedString有一些局限性,但它对 Cocoa 来说非常重要,因此编写自定义代码来处理本地化是不合理的,这意味着您必须使用它。也就是说,使用一些工具会有所帮助,下面是我的进展方式:

更新字符串文件

genstrings覆盖您的字符串文件,丢弃所有以前的翻译。 我编写了 Update _ string. 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 本地化更有效率!

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

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

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

下面是我的 Custom 实现,它使用了使用注释作为默认值的 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]

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

#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 脚本,这样你的 Localizable.string 文件将在每次构建时更新,也就是说,新的本地化字符串将添加到 Localized.string 文件中:

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

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

因此,当您在代码中添加这个新行时:

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

然后执行构建,您的./Localizable.script 文件将包含以下新行:

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

由于 key = = value 用于‘ view _ sets _ title’,定制的 LocalizedStringHandler 将返回注释,即‘ Settings’

好了

#define PBLocalizedString(key, val) \


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

至于 Xcode 的字符串自动补全,你可以试试 https://github.com/questbeat/Lin

我编写了一个脚本来帮助用多种语言维护 Localizable.string。虽然它不帮助自动完成,但有助于合并。字符串文件使用命令:

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

更多信息见: Https://github.com/hiroshi/merge_strings

我希望你们中的一些人会觉得这很有用。

在 iOS7和 Xcode 5中,应该避免使用‘ Localization.string’方法,而使用新的‘ base localization’方法。有一些教程周围,如果你谷歌’基本本地化’

苹果文档: 基地定位

我自己,我经常忘记编码,忘记把条目放进。字符串文件。因此,我有帮助程序脚本来查找我应该将什么内容放回去。字符串文件和翻译。

当我在 NSLocalizedString 上使用我自己的宏时,为了简单起见,我假设 使用前请检查和更新脚本被用作 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.string 文件中找到的键。下面是一个例子:

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

当然可以擦得更亮一些,不过我想我可以分享一下。

如果有人在寻找 Swift 解决方案,你可能想看看我的解决方案: SwiftyLocalization

只需要几个步骤就可以在 Google 电子表格中实现非常灵活的本地化(注释、自定义颜色、突出显示、字体、多个工作表等等)。

简而言之,步骤是: Google 电子表格—— > CSV 文件—— > Localizable.string

此外,它还生成 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 Script 来转换 Sheets —— > CSV,Python Script 来转换 CSV 文件—— > Localizable.string 你可以快速浏览一下这个示例表来了解可能的情况。

在 Swift 中,我使用了以下方法,例如,在这个例子中,用于表示按钮“ Yes”:

NSLocalizedString("btn_yes", value: "Yes", comment: "Yes button")

请注意默认文本值的 value:用法。第一个参数用作翻译 ID。使用 value:参数的优点是可以稍后更改默认文本,但翻译 ID 保持不变。String 文件将包含 "btn_yes" = "Yes";

如果没有使用 value:参数,那么第一个参数将同时用于翻译 ID 和默认文本值。String 文件将包含 "Yes" = "Yes";。这种管理本地化文件的方式似乎有些奇怪。特别是如果翻译的文本很长,那么 ID 也很长。每当默认文本值的任何字符发生更改时,翻译 ID 也会发生更改。这将导致在使用外部翻译系统时出现问题。更改翻译 ID 被理解为添加新的翻译文本,这可能并不总是需要的。