Как локализовать строки, которые преимущественно состоят из переменных - эффективно как разработчик и интуитивно как переводчик

У меня есть сотни динамических строк, т.е. в этой строке существует одна или несколько переменных, которые динамически генерируются в коде. Например, это строка с чем-либо в ‹›, представляющая переменную, значение которой неизвестно, пока код не будет выполнен:

‹Преимущественно облачно›, до ‹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 %@.";

Благодаря https://stackoverflow.com/a/56445894/698971 я создал расширение String, которое возвращает локализованную строку с переданными аргументами:

/// 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)
}

И получить последнюю строку для пользовательского интерфейса так же просто, как позвонить:

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

Но это не идеально по нескольким причинам. Строка в файле .strings не удобна для чтения. Комментарии необходимы для понимания того, что представляет собой каждая переменная. А затем представьте ситуацию, в которой необходимо изменить порядок переменных, чтобы строка читалась естественным образом на языке. Это нужно как-то отслеживать, а затем мне также приходится вмешиваться в код, чтобы гарантировать, что порядок, который я передаю в аргументах, изменился соответствующим образом.

Альтернатива, о которой я подумал, может частично решить эту проблему (но имеет свои проблемы - подробнее об этом позже) - это переместить строки в код. Например, есть функция:

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(...)
 }
}

Это не конец света, но это означает, что каждый раз, когда я добавляю новый язык, мне нужно будет добавлять новый регистр для каждой из этих функций, указывающих на соответствующую языковую функцию для строки.

Есть ли вариант, который удобен в зависимости от файлов .strings, но обеспечивает удобочитаемость описательных имен переменных в строках для работы переводчиков?


person Barrrdi    schedule 21.05.2021    source источник
comment
Порядок см. Там stackoverflow.com/questions/1063843/ работает только с "hourSeries_const" = "%@ with %@ %@ to %@ by %@.";, а не "\(weather) with \(dataPoint) \(valueDirection) to \(valueHighlight) by \(highlightedValueTime).". Возможно, вас заинтересует SwiftGen github.com/SwiftGen/SwiftGen#strings   -  person Larme    schedule 21.05.2021
comment
Вы также можете использовать заполнители: "hourSeries_const" = [[Weather]] with to [[Temperature]] by [[Time]] и заменять их. Это может означать парсер. Обратите внимание, как работает SwiftGen, вы можете использовать для этого собственный шаблон Stencil (но это означает, что некоторые из них работают на вашей стороне).   -  person Larme    schedule 21.05.2021
comment
вы должны использовать следующий% @ для строк% d для целых чисел и% f, я думаю, для чисел с плавающей запятой. ищите их, и вы найдете ответы. в зависимости от того, какую библиотеку вы используете, например Localize_Swift, вы сделаете что-то вроде этого Label.Text =% @. localizedFormat (ваша переменная, которая заменит @)   -  person Mahmoud Zinji    schedule 21.05.2021
comment
Всем спасибо за совет. Ларме, решение, которое я выбрал ниже, по сути является (если я неправильно понял) тем, что вы предложили.   -  person Barrrdi    schedule 21.05.2021


Ответы (1)


Благодаря Пакет локализации предоставил именно то, что я искал (и я считаю, что он делает, по сути, то, что предложил Ларм в комментарии выше).

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}.";

И передал пользовательскому интерфейсу в таком коде:

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