如何本地化主要由变量组成的字符串——像开发人员一样高效,像翻译人员一样直观

How to localise strings that pre-dominantly consist of variables – efficiently as dev and intuitively as translator

我有数百个动态字符串,即这些字符串中存在一个或多个变量,这些变量是在代码中动态生成的。例如,这是一个在 <> 中包含任何内容的字符串,表示在执行代码之前其值未知的变量:

<大部分多云> <25°> 到 <2pm>

由于这些字符串将被本地化,理想情况下我会依赖 .strings 文件来存储它们。这是上面的例子,如果我要在那里定义的话:

/*
    One condition throughout an hourly range
    e.g. <Mostly cloudy> with <temperatures> <rising> to <25°> by <2pm>.
    
    Parameters:
    1- weather
    2- measurement point (default=temperature)
    3- measurement trajectory (upwards or downwards)
    4- measurement value
    5- time above value is reached
 */
"hourSeries_const" = "%@ with %@ %@ to %@ by %@.";

感谢 ,我创建了一个字符串扩展,returns 本地化字符串及其传入的参数:

/// Fetches a localised String Arguments
///
/// - Parameter arguments: parameters to be added in a string
/// - Returns: localized string
public func localized(with arguments: [CVarArg]) -> String {
    return String(format: self.localized, locale: nil, arguments: arguments)
}

获取 UI 的最终字符串就像调用一样简单:

let a = "hourSeries_const".localized(with: ["Mostly cloudy","temperatures","rising","24°","2pm"])

但由于几个原因,这并不完美。 .strings 文件中的字符串不会 reader 友好。注释对于理解每个变量代表什么至关重要。然后想象需要重新排列变量顺序的情况,以便字符串在某种语言中自然地读取。这需要以某种方式进行跟踪,然后我也必须干预代码以确保我在参数中传递的顺序相应地改变。

我想到的替代方案可能会部分解决这个问题(但有其自身的问题 - 稍后会详细介绍)是将字符串移动到代码中。例如,有一个函数:

func hourSeries_const(weather:String, dataPoint:String, valueDirection:String, valueHighlight:String, highlightedValueTime:String) -> String {
  return "\(weather) with \(dataPoint) \(valueDirection) to \(valueHighlight) by \(highlightedValueTime)."
}

但是由于要支持多种语言,我需要 switch 才能在不同语言之间进行选择。这并不理想,因为我计划向每位翻译人员发送他们的语言文件以供使用,即它应该只包含他们语言的字符串。我可以通过添加调用所选语言函数的选择器函数来解决:

func hourSeries_const(...) -> String {
 switch language {
  case "en": return hourSeries_const_en(...)
  case "de": return hourSeries_const_de(...)
 }
}

这不是世界末日,但这确实意味着每次我添加一种新语言时,我都需要为这些函数中的每一个添加一个新的 case 指向该语言对应的字符串函数.

是否有一个选项既可以方便地依赖 .strings 文件,又可以提供字符串中描述性变量名称的可读性以供翻译人员使用?

感谢 Sam Deane’s Localization 包提供了我所追求的(我相信基本上是 Larme 在上面的评论中建议的)。

public class Localization {
    static var bundlesToSearch: [Bundle] = [Bundle.main]
    
    public class func registerLocalizationBundle(_ bundle: Bundle) {
        bundlesToSearch.append(bundle)
    }
}

extension String {
    /**
     Look up a localized version of the string.
     If a bundle is specified, we only search there.
     If no bundle is specified, we search in a set of registered bundles.
     This always includes the main bundle, but can have other bundles added to it, allowing you
     to automatically pick up translations from framework bundles (without having to search through
     every loaded bundle).
    */
    
    public func localized(with args: [String:Any], tableName: String? = nil, bundle: Bundle? = nil, value: String = "", comment: String = "") -> String {
        var string = self
        let bundlesToSearch = bundle == nil ? Localization.bundlesToSearch : [bundle!]
        
        for bundle in bundlesToSearch {
            string = NSLocalizedString(self, tableName: tableName, bundle: bundle, value: value, comment: comment)
            if string != self {
                break
            }
        }
        
        for (key, value) in args {
            string = string.replacingOccurrences(of: "{\(key)}", with: String(describing: value))
        }
        return string
    }
}

感谢以上内容,我得到了我想要的东西。字符串在 Localizable.strings:

中这样定义
"hourSeries_const" = "{weather} with {point} {direction} to {value} by {time}.";

并通过如下代码传递给 UI:

"hourSeries_const".localized(with: ["weather":"Mostly cloudy","point":"temperatures","direction":"rising","value":"24°","time":"2pm"])