【问题标题】:Best practice using NSLocalizedString使用 NSLocalizedString 的最佳实践
【发布时间】:2012-04-11 08:14:35
【问题描述】:

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

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

  • Xcode 中的字符串没有自动完成功能。这使得工作不仅容易出错,而且令人厌烦。
  • 您最终可能只是因为不知道已经存在等效字符串而重新定义了一个字符串(即“请输入密码”与“先输入密码”)
  • 与自动补全问题类似,您需要“记住”/复制粘贴注释字符串,否则 genstring 最终会为一个字符串生成多个 cmets
  • 如果您想在已经本地化了一些字符串之后使用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 为不同的本地化问题(如控制器、模型等)创建不同的字符串文件并创建初始默认值。
  • 似乎 xcode6 的导出到本地化并没有捕获头文件中定义为宏的字符串。谁能确认或告诉我我可能遗漏了什么?谢谢...!
  • @Juddster,可以确认,即使使用新基金 Editor->Export for Localization 也不会在头文件中找到

标签: objective-c ios localization nslocalizedstring


【解决方案1】:

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

【讨论】:

  • 这实际上是相当惊人的。无需创建宏。
  • 找不到页面_
  • @Juanmi 感谢您提及死链接。我用 github url 替换了链接。
【解决方案2】:

在 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 被理解为添加新的翻译文本,这可能并不总是需要的。

【讨论】:

    【解决方案3】:

    如果有人在寻找 Swift 解决方案。您可能想查看我在这里整理的解决方案: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 您可以快速查看此示例表以了解可能的情况。

    【讨论】:

      【解决方案4】:

      我自己,经常被编码冲昏头脑,忘记将条目放入 .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"
      

      当然可以进一步完善,但我想我会分享。

      【讨论】:

        【解决方案5】:

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

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

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

        希望你们中的一些人觉得它有用。

        【讨论】:

          【解决方案6】:

          在 iOS 7 和 Xcode 5 中,您应该避免使用“Localization.strings”方法,而应使用新的“基本本地化”方法。如果你在谷歌上搜索“基础本地化”,这里有一些教程

          苹果文档:Base localization

          【讨论】:

          • 是的,史蒂夫,这是正确的。此外,对于任何动态生成的字符串,您仍然需要 .strings 文件方法。但仅针对这些,Apple 的首选方法是基础本地化。
          • 链接到新方法?
          • Imo 基本的本地化方法毫无价值。您仍然必须为动态字符串保留其他位置文件,并且它会使您的字符串分布在许多文件中。 Nibs/Storyboards 中的字符串可以通过一些 Lib 自动本地化为 Localizable.strings 中的键,例如 github.com/AliSoftware/OHAutoNIBi18n
          【解决方案7】:

          NSLocalizedString 有一些限制,但它对 Cocoa 来说非常重要,以至于编写自定义代码来处理本地化是不合理的,这意味着您必须使用它。也就是说,一些工具可以提供帮助,这就是我的操作方式:

          更新字符串文件

          genstrings 覆盖您的字符串文件,丢弃您之前的所有翻译。 我写了update_strings.py 来解析旧的字符串文件,运行genstrings 并填写空白,这样您就不必手动恢复现有的翻译。 该脚本尝试尽可能地匹配现有的字符串文件,以避免在更新它们时出现太大的差异。

          给你的字符串命名

          如果您像宣传的那样使用NSLocalizedString:

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

          您最终可能会在代码的另一部分定义相同的字符串,这可能会发生冲突,因为相同的英文术语在不同的上下文中可能具有不同的含义(想到OK 和Cancel)。 这就是为什么我总是使用一个无意义的全大写字符串和一个特定于模块的前缀和一个非常精确的描述:

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

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

          如果您多次使用相同的字符串,您可以像以前一样使用宏,也可以将其作为实例变量缓存在视图控制器或数据源中。 这样,您就不必重复可能会变得陈旧并且在相同本地化实例之间变得不一致的描述,这总是令人困惑。 由于实例变量是符号,您将能够对这些最常见的翻译使用自动完成功能,并为特定的翻译使用“手动”字符串,这无论如何只会出现一次。

          我希望通过这些技巧,您可以提高 Cocoa 本地化的工作效率!

          【讨论】:

          • 谢谢你的回答,我一定会看看你的python文件。我同意你的命名约定。我最近与其他一些 iOS 开发人员交谈过,他们建议使用静态字符串而不是宏,这是有道理的。我赞成您的回答,但在接受之前会稍等片刻,因为解决方案仍然有点笨拙。也许会出现更好的东西。再次感谢!
          • 不客气。本地化是一个乏味的过程,拥有正确的工具和工作流程会让世界变得不同。
          • 我一直不明白为什么 gettext 风格的本地化函数使用其中一种翻译作为关键。如果您的原始文本发生更改会怎样?您的密钥更改和所有本地化文件都使用旧文本作为密钥。这对我来说从来没有意义。我一直使用像“home_button_text”这样的键,所以它们是独一无二的,永远不会改变。我还编写了一个 bash 脚本来解析我所有的 Localizable.strings 文件,并使用静态方法生成一个类文件,该类文件将加载适当的字符串。这给了我代码完成。有一天我可能会开源这个。
          • 我认为你的意思是 genstrings 不是 gestring。
          • @ndfred 编译时检查您没有输入错误的字符串是最大的胜利。无论如何要添加更多的代码。同样在重构、静态分析的情况下,拥有一个符号会让事情变得更容易。
          【解决方案8】:
          #define PBLocalizedString(key, val) \
          
          [[NSBundle mainBundle] localizedStringForKey:(key) value:(val) table:nil]
          

          【讨论】:

            【解决方案9】:

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

            确保在您的应用构建阶段添加一个运行脚本,以便您的 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'的key == value,自定义LocalizedStringHandler将返回注释,即'Settings'

            瞧 :-)

            【讨论】:

            • 得到 ARC 错误,没有已知的选择器 'localizedString:comment::( 的实例方法
            • 我想这是因为 LocalizationHandlerUtil.h 丢失了。我找不到代码回来了...只需尝试创建头文件LocalizationHandlerUtil.h就可以了
            • 我已经创建了文件。我认为这是由于文件夹路径问题。
            猜你喜欢
            • 2012-12-31
            • 1970-01-01
            • 2017-11-29
            • 2010-10-08
            • 2010-12-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多