Чтобы объединить повторяющиеся сообщения в приложении для 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 описывает эти настройки схемы для проверки локализаций без изменения языка всей системы. Тестирование локализаций при запуске приложения.
Если результат неправильный, выполните следующие действия по порядку:
- Запись отсутствует: Убедитесь, что каталог включён в целевой объект приложения, и соберите соответствующую схему. Проверьте, включён ли параметр
Use Compiler to Extract Swift Strings. - Неожиданно отображается исходный текст: Проверьте ключ, таблицу, пакет и перевод, прежде чем считать читаемый резервный текст успешным результатом.
- Отличается только метка Interface Builder: Проверьте её пользовательский класс, модуль, путь загрузки и последующие присваивания.
- Отличаются только последующие значения: Убедитесь, что обеим интерфейсам передаются обновлённые значения модели.
- Неправильная форма множественного числа: Проверьте, какой числовой аргумент выбран для варьирования, и каждый применимый вариант.
Apple документирует настройку извлечения компилятором и объясняет, что переведённые записи, которых больше нет в исходном коде, могут оставаться помеченными как Stale. Это состояние указывает на необходимость очистки, но не доказывает, что ресурс больше не используется. Прежде чем удалять запись и её переводы, проверьте прежнее присваивание в интерфейсе и вызывающий код Swift. Знакомство с каталогами строк.
Объединение завершено, когда оба интерфейса получают одно и то же контекстное сообщение из единого определения, его аргументы остаются на месте, а прежние шаблоны предложения больше нигде не используются. Храните контракт и примеры для проверки рядом со вспомогательным методом, чтобы будущие изменения формулировки сохраняли общий смысл для обоих окон.
Источники
Локализация и варьирование текста с помощью каталога строк — Apple
Знакомство с каталогами строк — Apple
LocalizedStringKey — Apple
LocalizedStringResource — Apple
init(localized:defaultValue:table:bundle:locale:comment:) — Apple
NSTextField — Apple
awakeFromNib() — Apple