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

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

Что должен гарантировать контракт резервного варианта?

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

При выборе языка учитываются предпочтительные языки пользователя и доступные локализации. Отсутствие перевода на первый язык в списке не означает, что английский появится сразу: сначала может быть выбрано соответствие для другого предпочтительного языка. В архивном руководстве Apple по пакетам описана такая последовательность выбора, за которой следует язык разработки, если ни один предпочтительный язык не подошел. В актуальной документации CFBundleDevelopmentRegion определен как идентификатор резервного языка пакета. Доступ к содержимому пакета, CFBundleDevelopmentRegion.

Используйте эту таблицу решений, определяя ожидаемые результаты. Она разделяет выбор ресурса и поиск сообщения, а не считает любое отсутствие перевода одной и той же ситуацией.

Условие Контракт для сообщений, которыми вы управляете Что проверить
Для предпочтительного языка доступен перевод Отображать этот перевод Пакет, таблицу и ключ
Первый предпочтительный язык недоступен, но подходит другой предпочтительный язык Позволить системному сопоставлению языков выбрать доступную локализацию Порядок предпочтений и поставляемые ресурсы
Ни одна предпочтительная локализация не подходит Использовать ресурсы пакета на английском языке разработки Язык разработки и английские ресурсы
Поиск не может найти сообщение Возвращать явное английское значение по умолчанию для этого сообщения Параметр defaultValue сообщения
Поиск фреймворка по ошибке выполняется в пакете приложения Исправить аргумент пакета Владельца ресурса
Перевод есть, но его текст не подходит Исправить перевод Содержимое каталога и проверку

Таблица объединяет правила Apple для выбора ресурсов с документированным поведением значения по умолчанию в инициализаторе Swift. Это политика реализации, выведенная из этих API, а не отдельная функция резервного варианта в каталогах строк. Если поиск успешен, но найденный текст не подходит, это не соответствует документированному условию инициализатора для использования значения по умолчанию: неудачному поиску. String.init(localized:defaultValue:table:bundle:locale:comment:).

Настройте исходные ресурсы на английском языке для каждой целевой сборки-владельца

Для этого примера используйте актуальную версию Xcode, приложение SwiftUI для macOS с окном и целевой версией macOS 13 или новее, а также встроенную динамическую целевую сборку фреймворка с именем PanelKit. Свяжите этот фреймворк с приложением и включите его в приложение. Это предварительные условия для примера проекта; имя фреймворка не обозначает существующий продукт.

Установите английский как язык разработки для обеих целевых сборок и проверьте, что в собранном пакете каждой из них CFBundleDevelopmentRegion разрешается в en. Предоставьте соответствующие исходные ресурсы на английском языке в обеих целевых сборках. Идентификатор языка разработки задает резервный язык, но не предоставляет отсутствующее содержимое сообщения. Apple указывает en как идентификатор английского языка. CFBundleDevelopmentRegion.

Создайте отдельный каталог Localizable.xcstrings для каждой целевой сборки. В Xcode выберите File > New > File from Template, затем String Catalog и задайте имя Localizable. Назначьте один каталог целевой сборке приложения, а другой — целевой сборке фреймворка. Разделение ресурсов по владельцам позволяет независимо проверять каждый поиск. Apple описывает процесс создания каталогов и поддерживает использование нескольких каталогов. Локализация текста и управление его вариантами с помощью каталога строк.

Добавив приведенный ниже код, выберите Product > Build. Xcode извлечет распознанные вызовы локализации в каталоги. Убедитесь, что workspace.title появился в каталоге приложения, а panel.ready — в каталоге фреймворка, и что исходные значения на английском языке соответствуют замыслу. Добавьте поддерживаемые языки с помощью кнопки добавления на боковой панели каталога и введите переводы соответствующих сообщений. Локализация текста и управление его вариантами с помощью каталога строк.

Полноту каталога и корректность поиска во время выполнения нужно проверять отдельно. Переведенная запись в каталоге фреймворка не поможет, если вызов ищет строку в пакете приложения. Apple прямо рекомендует передавать пакет, связанный с целевой сборкой, если код загружает строки не из основного пакета. Подготовка текста приложения к переводу.

Задайте для каждого сообщения пакет, ключ, таблицу и значение по умолчанию

Используйте стабильные условные ключи и понятные английские значения по умолчанию. Инициализатор Swift, принимающий defaultValue, поддерживает ключ, отличный от отображаемого исходного текста. Значение по умолчанию возвращается, если поиск локализованной строки в пакете завершается неудачно. Параметр table задает таблицу строк; если его опустить, будет выбрана Localizable. String.init(localized:defaultValue:table:bundle:locale:comment:).

Размещайте сообщения фреймворка во фреймворке

Добавьте этот полный файл исходного кода Swift в целевую сборку динамического фреймворка PanelKit. Класс-маркер определяется в этой целевой сборке, поэтому Bundle(for:) указывает на ее пакет. Apple описывает такой подход с классом как альтернативу актуальному макросу #bundle. Подготовка текста приложения к переводу.

import Foundation

private final class PanelBundleAnchor: NSObject {}

public enum PanelText {
    public static var resourceBundle: Bundle {
        Bundle(for: PanelBundleAnchor.self)
    }

    public static var ready: String {
        String(
            localized: "panel.ready",
            defaultValue: "Panel ready",
            table: "Localizable",
            bundle: resourceBundle,
            comment: "Status label above the framework panel."
        )
    }
}

Фреймворк выполняет поиск своего сообщения, прежде чем вернуть приложению значение String. Так устанавливается четкая граница владения: фреймворк управляет ключом, исходным текстом, таблицей и пакетом ресурсов. Приложение получает готовый текст для отображения.

Покажите сообщения приложения и фреймворка вместе

Замените сгенерированный файл точки входа приложения этим полным примером. Он предполагает, что подключенный модуль фреймворка называется PanelKit и содержит приведенный выше файл. В целевой сборке приложения должна остаться только одна точка входа.

import Foundation
import SwiftUI
import PanelKit

@main
struct FallbackDemoApp: App {
    var body: some Scene {
        WindowGroup {
            VStack(alignment: .leading, spacing: 12) {
                Text(verbatim: String(
                    localized: "workspace.title",
                    defaultValue: "Workspace",
                    table: "Localizable",
                    bundle: .main,
                    comment: "Heading for the main workspace."
                ))

                Text(verbatim: PanelText.ready)
            }
            .padding(24)
        }
    }
}

Поиск приложения явно использует .main; поиск фреймворка — его собственный пакет ресурсов. Для обоих заданы значения по умолчанию на английском языке. Это соответствует документированным Apple параметрам выбора пакета и значения по умолчанию. Соберите и запустите проект в Xcode, чтобы проверить конфигурацию целевых сборок; эти примеры здесь не запускались. String.init(localized:defaultValue:table:bundle:locale:comment:).

Если существующая реализация использует Bundle.localizedString(forKey:value:table:), укажите непустое понятное значение value, если при отсутствии ключа нужно возвращать читаемый текст. Apple указывает, что при отсутствии или пустом value для отсутствующего ключа возвращается сам ключ, а при непустом значении — value. Поэтому для ключа вроде panel.ready требуется явный резервный текст, если отображение этого идентификатора нарушает контракт сообщения. localizedString(forKey:value:table:).

Решите, могут ли фреймворки выбирать другие языки

Передача правильного пакета определяет, где выполнять поиск. Конфигурация смешанной локализации влияет на то, как выбор локализации связан с содержащим приложение. Apple предоставляет логический ключ CFBundleAllowMixedLocalizations, отображаемый как Localized resources can be mixed, для получения локализованных строк из фреймворков. CFBundleAllowMixedLocalizations.

В пояснении Swift Forums сказано, что включение этого ключа в Info.plist приложения позволяет другим пакетам выбирать локализации помимо тех, которые поддерживает основное приложение. Без этого явного разрешения основной пакет ограничивает их выбор. Это более раннее пояснение; оно полезно в дополнение к актуальной справке Apple по ключу. Локализация Swift Package Manager.

Для примера с окном предпочтительно обеспечить одинаковый набор поддерживаемых языков в приложении и фреймворке и добавить ресурсы для разработки на английском в оба пакета. Не включайте независимый выбор языка фреймворком в первоначальный контракт. Если же вы хотите, чтобы фреймворк использовал дополнительные переводы, установите логическое значение YES для ключа CFBundleAllowMixedLocalizations приложения и явно проверьте эту конфигурацию. Локализация Swift Package Manager.

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

Не используйте аргумент locale этого инициализатора String для переключения языка перевода. Apple прямо указывает, что он управляет локализацией интерполированных значений, например чисел, и не меняет локаль, используемую для поиска локализованной строки. Для этой задачи используйте предусмотренную конфигурацию языков и проверьте элементы управления. String.init(localized:defaultValue:table:bundle:locale:comment:).

Проверяйте отсутствие языков отдельно от отсутствия сообщений

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

Используйте эту матрицу тестов как вспомогательный инструмент для диагностики проекта. Для проверки маршрутизации по пакетам временно задайте разные английские тестовые маркеры, например App translation sample и Framework translation sample, в поддерживаемой локализации, отличной от исходной. Это диагностические строки, а не переводы или идентификаторы языков.

  1. Проверьте исходное поведение. Запустите приложение с выбранным английским языком. Ожидаются Workspace и Panel ready.
  2. Проверьте маршрутизацию к переведенным ресурсам. Выберите поддерживаемую тестовую локализацию. Ожидаются разные маркеры из двух каталогов-владельцев.
  3. Проверьте значение по умолчанию для отсутствующего сообщения. В отдельной тестовой правке замените ключ поиска фреймворка на panel.ready.probe, оставьте defaultValue без изменений и исключите пробную запись из тестового каталога. Если поиск завершится неудачно, должно появиться Panel ready. После этого восстановите исходный ключ.
  4. Проверьте выбор языка, который не поддерживается. Используйте список предпочтений, в котором нет ни одного соответствующего поддерживаемого языка. Убедитесь, что оба управляемых пакета используют ресурсы для разработки на английском. Также проверьте случай, когда первым в списке стоит неподдерживаемый язык, а за ним — поддерживаемый: ожидаемый результат в этом случае отличается.
  5. Проверьте смешанную локализацию, если она включена. Проверьте язык, доступный только во фреймворке. Запишите ожидаемое поведение и сопоставьте с ним настройку смешанной локализации приложения.

Третья проверка изолирует неудачный поиск, а четвертая — сопоставление языков. Проверка только непереведенной строки каталога может смешать эти условия, поскольку итоговая конфигурация ресурсов также влияет на поиск. Ожидаемый результат при отсутствии ключа задается инициализатором Swift, а выбор предпочтительного языка подчиняется правилам пакета. String.init(localized:defaultValue:table:bundle:locale:comment:), Доступ к содержимому пакета.

Если результат отличается от ожидаемого, проверьте фактический пакет-владелец. developmentLocalization возвращает значение, соответствующее CFBundleDevelopmentRegion; preferredLocalizations возвращает доступные локализации в порядке, определяемом языковыми предпочтениями. Сравните эти свойства у Bundle.main и PanelText.resourceBundle. Они помогают диагностировать конфигурацию, но окончательной проверкой каждого поиска остаются отображаемые сообщения. developmentLocalization, preferredLocalizations.

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

Источники