Чтобы объединить повторяющиеся сообщения в приложении для Mac, назначьте каждому общему смыслу один ключ локализации и одно определение Swift, а затем используйте его везде, где появляется это сообщение. Храните вместе аргументы, правила множественного числа и контекст для переводчика; совпадения одного лишь английского текста недостаточно.

Это руководство предназначено для разработчика приложений для Mac, который поддерживает сводку выбранных элементов и в SwiftUI, и в окне Interface Builder. Конкретная задача — единообразно отображать сообщения вроде 2 items selected in Batch A., не поддерживая отдельные шаблоны предложений в XIB и коде Swift. Все приведённые ниже числа и названия условны.

Определите, какие повторяющиеся сообщения следует объединить

Прежде чем редактировать каталоги, определите контракт сообщения: что означает предложение, какие значения оно принимает и где отображается. Для этого примера контракт выглядит так:

Элемент контракта Определение
Значение Количество выбранных элементов в именованной коллекции
Ключ selection.summary
Таблица Localizable
Пакет Основной пакет приложения
Аргумент 1 Количество выбранных элементов типа Int64
Аргумент 2 Буквальное название коллекции, например Batch A
Вариация Форма множественного числа зависит от количества выбранных элементов
Контекст Полное предложение состояния, отображаемое в SwiftUI и в метке AppKit

Используйте этот контракт для проверки того, можно ли объединить сообщения. Два вхождения можно объединить, если совпадают их смысл, аргументы и грамматическая роль. Количество выбранных элементов и общее количество элементов следует оставить раздельными, даже если оба сообщения сейчас отображаются как 2 items. Кнопке с названием Select также нужно собственное сообщение, поскольку она обозначает действие.

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

Считайте таблицу и пакет частью контракта поиска. Ключ в другой таблице является отдельной целью поиска. Apple описывает выбор таблицы с помощью параметра table или tableName и рекомендует передавать локализуемый текст через LocalizedStringResource. В этом примере храните вспомогательный код и каталог в целевом объекте приложения и используйте основной пакет. Подготовка текста приложения к переводу.

Поймите различие между извлечением и форматированием во время выполнения

Извлечение выполняется при сборке в Xcode: система находит поддерживаемые локализуемые выражения и синхронизирует их исходный текст с каталогами. Локализация во время выполнения происходит, когда приложение разрешает сообщение и подставляет фактические значения. Наличие записи в каталоге доказывает, что система обнаружила её при извлечении; это не доказывает, что каждый элемент управления отображает её правильно.

У Interface Builder есть собственный процесс работы с ресурсами. Apple описывает каталоги, связанные со сторибордом или XIB, тогда как исходный код Swift обычно относится к таблице Localizable. В документированном процессе не описан межресурсный псевдоним, объединяющий сообщения лишь потому, что их видимый текст совпадает. Практичный поддерживаемый вариант — назначить общее локализованное сообщение из Swift после загрузки интерфейса. Это реализация, основанная на документированных API Apple. Знакомство с каталогами строк.

SwiftUI также различает буквальный ключ локализации и обычное строковое значение. Например, Text("Summary") запрашивает локализацию, а передача переменной типа String отображает её значение без такого поиска. Оборачивание строки времени выполнения в LocalizedStringKey запрашивает поиск, но не доказывает, что при извлечении были обнаружены все возможные значения. LocalizedStringKey.

Поэтому в приведённом ниже решении извлекаемый литерал находится внутри единственного вспомогательного метода. Он передаёт количество и название в LocalizedStringResource, а затем разрешает этот ресурс там, где нужна готовая строка. Apple описывает этот тип ресурса как способ отложить поиск локализации. Он доступен в macOS 13 и новее, которая является минимальной целевой версией для этого примера. LocalizedStringResource.

Определите общее сообщение один раз в Swift

Создайте проект приложения macOS с использованием SwiftUI, задайте целевую версию развёртывания macOS 13 или новее и добавьте Localizable.xcstrings в целевой объект приложения. В актуальной документации Xcode путь создания указан как File > New > File from Template; найдите string и выберите String Catalog. Локализация и варьирование текста с помощью каталога строк.

Замените сгенерированную точку входа приложения полной программой ниже. Удалите исходную точку входа, чтобы в целевом объекте осталось только одно объявление @main. Окно SwiftUI работает независимо от XIB; подкласс AppKit пригодится при адаптации существующей метки Interface Builder в следующем разделе.

import AppKit
import Foundation
import SwiftUI

enum SelectionMessages {
    static func summary(
        count: Int64,
        collectionLabel: String
    ) -> LocalizedStringResource {
        LocalizedStringResource(
            "selection.summary",
            defaultValue: "\(count) items selected in \(collectionLabel).",
            table: "Localizable",
            comment: "Complete selection-status sentence used in SwiftUI and an AppKit label. Argument 1 is the selected-item count; argument 2 is a literal collection label. Pluralize by argument 1."
        )
    }
}

struct SelectionDemoView: View {
    private let counts: [Int64] = [0, 1, 2]
    private let collectionLabel = "Batch A"

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            ForEach(counts, id: \.self) { count in
                Text(String(localized: SelectionMessages.summary(
                    count: count,
                    collectionLabel: collectionLabel
                )))
            }
        }
        .padding(24)
        .frame(minWidth: 360, minHeight: 160)
    }
}

final class SelectionSummaryField: NSTextField {
    override func awakeFromNib() {
        super.awakeFromNib()
        // Synthetic initial state for the Interface Builder example.
        updateSelection(count: 2, collectionLabel: "Batch A")
    }

    func updateSelection(count: Int64, collectionLabel: String) {
        stringValue = String(localized: SelectionMessages.summary(
            count: count,
            collectionLabel: collectionLabel
        ))
    }
}

@main
struct SelectionDemoApp: App {
    var body: some Scene {
        WindowGroup {
            SelectionDemoView()
        }
    }
}

Статический ключ идентифицирует сообщение; интерполируемое значение по умолчанию задаёт исходное предложение и аргументы времени выполнения. Инициализатор также принимает таблицу, пакет и комментарий для переводчика. По умолчанию используется основной пакет. Инициализатор LocalizedStringResource с явным ключом.

Название Batch A — это пример содержимого, вставляемого в предложение. Это не ещё один ключ сообщения. Такое различие позволяет отделить названия коллекций от окружающего текста интерфейса. Если в вашем приложении используются заранее заданные локализованные названия коллекций, локализуйте их с помощью собственных определений, прежде чем передавать сюда.

Не собирайте это предложение из независимо локализованных частей, таких как количество, items selected in и название. Если полное предложение входит в контракт, переводчики получают единое целое и могут согласованно менять формулировку и порядок аргументов.

Направьте метку Interface Builder к тому же определению

Для существующего XIB или сториборда используйте статическую метку NSTextField для сводки. Задайте её пользовательский класс SelectionSummaryField и укажите целевой объект приложения в поле модуля. Удалите прежнее предложение из строки, заданной при проектировании, поскольку теперь эта метка получает текст из общего определения Swift.

При загрузке интерфейса подкласс вызывает super.awakeFromNib() и назначает условную начальную сводку. Apple описывает awakeFromNib() как метод, вызываемый после загрузки, когда выходы и действия уже связаны, и требует вызывать реализацию суперкласса. awakeFromNib().

В реальном окне вызывайте updateSelection(count:collectionLabel:) для подключённой метки при каждом изменении выбора, передавая те же значения модели, что и в SwiftUI. Замените условное начальное присваивание фактическим путём инициализации окна. Однократное присваивание при загрузке не сможет отслеживать последующие изменения выбора.

Адаптер AppKit задаёт stringValue — документированный способ назначить текст для NSTextField. При этом компоновка остаётся в Interface Builder, а сообщение передаётся в управление Swift. Предусмотрите достаточную ширину или перенос текста для полного предложения. NSTextField.

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

Настройте варианты множественного числа, не меняя аргументы

Выберите Product > Build, откройте Localizable.xcstrings и найдите selection.summary. Щёлкните запись правой кнопкой мыши, выберите Vary by Plural и при появлении запроса Xcode укажите, по какому числовому аргументу следует создавать варианты. Apple описывает этот процесс для интерполируемых сообщений. Локализация и варьирование текста с помощью каталога строк.

Для исходной английской локализации настройте единственное число так, чтобы оно давало 1 item selected in Batch A., а остальные варианты — предложения вроде 2 items selected in Batch A. Сохраните оба заполнителя аргументов, предоставленных Xcode. Изменяйте окружающий текст, не заменяя заполнитель примером количества или названия.

Условный ввод Ожидаемый результат на английском после настройки множественного числа
Количество 0, название Batch A 0 items selected in Batch A.
Количество 1, название Batch A 1 item selected in Batch A.
Количество 2, название Batch A 2 items selected in Batch A.
Количество 2, название Batch B 2 items selected in Batch B.

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

Изменение названия в последней строке полезно для диагностики: если на экране остаётся Batch A, возможно, в варианте жёстко задано примерное значение или вызывающий код передаёт устаревшее состояние. Если название исчезло, проверьте заполнители в варианте, прежде чем менять сигнатуру вспомогательного метода.

Проверьте все способы отображения, прежде чем удалять старые записи

Проверьте окно SwiftUI и фактическое окно Interface Builder отдельно. В обоих используйте одинаковые количества и названия. Проверьте начальную загрузку, последующее изменение выбора и более длинное условное название, например Batch A Extended. Убедитесь, что формулировки совпадают и предложение остаётся удобочитаемым.

Для каждой поддерживаемой локализации выберите Product > Scheme > Edit Scheme, затем Run, откройте Options и задайте App Language. Запустите приложение ещё раз и проверьте оба способа отображения. Apple описывает эти настройки схемы для проверки локализаций без изменения языка всей системы. Тестирование локализаций при запуске приложения.

Если результат неправильный, выполните следующие действия по порядку:

  1. Запись отсутствует: Убедитесь, что каталог включён в целевой объект приложения, и соберите соответствующую схему. Проверьте, включён ли параметр Use Compiler to Extract Swift Strings.
  2. Неожиданно отображается исходный текст: Проверьте ключ, таблицу, пакет и перевод, прежде чем считать читаемый резервный текст успешным результатом.
  3. Отличается только метка Interface Builder: Проверьте её пользовательский класс, модуль, путь загрузки и последующие присваивания.
  4. Отличаются только последующие значения: Убедитесь, что обеим интерфейсам передаются обновлённые значения модели.
  5. Неправильная форма множественного числа: Проверьте, какой числовой аргумент выбран для варьирования, и каждый применимый вариант.

Apple документирует настройку извлечения компилятором и объясняет, что переведённые записи, которых больше нет в исходном коде, могут оставаться помеченными как Stale. Это состояние указывает на необходимость очистки, но не доказывает, что ресурс больше не используется. Прежде чем удалять запись и её переводы, проверьте прежнее присваивание в интерфейсе и вызывающий код Swift. Знакомство с каталогами строк.

Объединение завершено, когда оба интерфейса получают одно и то же контекстное сообщение из единого определения, его аргументы остаются на месте, а прежние шаблоны предложения больше нигде не используются. Храните контракт и примеры для проверки рядом со вспомогательным методом, чтобы будущие изменения формулировки сохраняли общий смысл для обоих окон.

Источники