i18n & l10n

在使用国际化和本地化时,经常遇到两个术语:“i18n” 和 “l10n”,它们分别是“国际化”和“本地化”的缩写。为什么?嗯,这很简单:

这两者都旨在使键入更容易,并且在存储文件目录时尤为常见。例如,您可能会看到存储在 i18n 目录中的语言文件。这是最容易的部分。稍微令人困惑的部分是这两个词之间的区别:


Internationalization 国际化

<aside> 💡 Xcode 中的一个重要设置:转到【Product】菜单,按住 Option 键,然后选择【Run】。然后转到弹窗中的【Option】选项卡,然后选中 “本地化调试 Localization Debugging ” 旁边的框【显示非本地化字符串 Show non-localized strings】。

</aside>

此选项将自动调整应用程序在模拟器中的外观,以便任何未本地化的文本都将以大写字母显示。这一开始可能会让人困惑,尤其是前面所说的“国际化和本地化”是两回事。但是,一旦完成了应用程序的国际化,其实也已经完成了本地化(因为你已经添加了原始语言)。

该选项开启后:


本地化为英文

第一步是要本地化英文 (Localizing to English) ,要从应用程序中提取英文字符串,并将它们存储在其他地方。这些将保留为英文,但一旦完成,我们的应用程序将国际化,它将准备好与我们想要添加的任何其他语言一起使用。

1. 手动创建 Localizable 文件

现在,查看本地化文件可以看到:

2. 自动创建 Localizable 文件

前期,在初始本地化中,相当一部分工作是要把相同的字符串作为键和值。也就是先把要本地化的词全部摘出来,方便后续修改。查找和提取代码中的所有字符串看似非常麻烦,所以 Xcode 附带了一个非常有用的命令行工具,使这个过程变得容易。

3. 解决自动创建的报错

在执行 genstrings 命令时,可能无法识别带有 formatter 的 Text,例如 Text(issue.formatted(date: .numeric, time: .omitted))

  1. 最简单的方法,创建计算属性:

    // 解决这类问题的一种简单方法是:将代码改成没有问题的简单的东西。把视图中的逻辑移动到视图模型中,尽量让数据和视图分离
    var issueFormattedCreationDate: String {
        issueCreationDate.formatted(date: .numeric, time: .omitted)
    }
    
    // 挪过去以后,视图中的代码就比较简单了
    Text(issue.issueFormattedCreationDate)
    
    var formattedDate: String {
        NSLocalizedString(
            dateFormatter.string(from: project.projectCreationDate),
            comment: "Project creation date"
        )
    }
    
    // 使用
    Text(formattedDate)
    
  2. 更结构化的方法 - 创建扩展:

    extension Date {
        func localizedString(using formatter: DateFormatter) -> String {
            NSLocalizedString(
                formatter.string(from: self),
                comment: "Formatted date"
            )
        }
    }
    
    // 使用
    Text(project.projectCreationDate.localizedString(using: dateFormatter))
    
  3. 如果需要在多处使用相同格式的日期:

    struct LocalizedDateFormat {
        static func format(_ date: Date, style: DateFormatter.Style = .medium) -> String {
            let formatter = DateFormatter()
            formatter.dateStyle = style
    
            return NSLocalizedString(
                formatter.string(from: date),
                comment: "Date formatted with \\\\(style) style"
            )
        }
    }
    
    // 使用
    Text(LocalizedDateFormat.format(project.projectCreationDate))
    
  4. 如果你的日期格式需要本地化:

    extension DateFormatter {
        static var localizedProjectDate: DateFormatter = {
            let formatter = DateFormatter()
            // 设置日期格式的本地化键
            formatter.dateFormat = NSLocalizedString(
                "yyyy-MM-dd",
                comment: "Project date format"
            )
            return formatter
        }()
    }
    
    // 使用
    Text(project.projectCreationDate, formatter: .localizedProjectDate)
    
  5. 完整的解决方案示例:

    // DateFormatting.swift
    enum DateFormatting {
        // 日期格式化器
        static let projectDateFormatter: DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateStyle = .medium
            formatter.timeStyle = .none
            return formatter
        }()
    
        // 格式化方法
        static func localizedProjectDate(_ date: Date) -> String {
            NSLocalizedString(
                projectDateFormatter.string(from: date),
                comment: "Project creation date format"
            )
        }
    
        // 带有自定义格式的方法
        static func localizedCustomDate(_ date: Date, format: String) -> String {
            let formatter = DateFormatter()
            formatter.dateFormat = NSLocalizedString(
                format,
                comment: "Custom date format"
            )
            return NSLocalizedString(
                formatter.string(from: date),
                comment: "Formatted date with custom format"
            )
        }
    }
    
    // 在视图中使用
    struct ProjectView: View {
        let project: Project
    
        var body: some View {
            VStack {
                // 基本使用
                Text(DateFormatting.localizedProjectDate(project.projectCreationDate))
    
                // 自定义格式使用
                Text(DateFormatting.localizedCustomDate(
                    project.projectCreationDate,
                    format: "yyyy年MM月dd日"
                ))
            }
        }
    }
    
    
  6. 如果需要在 Localizable.strings 中定义不同的日期格式:

    // Localizable.strings (English)
    "DATE_FORMAT" = "MMM d, yyyy";
    "PROJECT_DATE_%@" = "Created on %@";
    
    // Localizable.strings (Chinese)
    "DATE_FORMAT" = "yyyy年MM月dd日";
    "PROJECT_DATE_%@" = "创建于 %@";
    
    // 在代码中使用
    struct ProjectDateView: View {
        let date: Date
    
        var formattedDate: String {
            let formatter = DateFormatter()
            formatter.dateFormat = NSLocalizedString("DATE_FORMAT", comment: "Date format")
            let dateString = formatter.string(from: date)
            return String(format: NSLocalizedString("PROJECT_DATE_%@", comment: "Project creation date"), dateString)
        }
    
        var body: some View {
            Text(formattedDate)
        }
    }
    

4. 本地化导致的编译报错

生成本地化文本后,有时会导致编译出错,这时可以排查以下常见原因:

5. 整理 Localizable 文件