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

Что должен гарантировать повторно используемый API текста?

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

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

В следующей таблице решений приведен вариант проектирования API, основанный на описанном поведении. Явные метки аргументов позволяют проверить намерение вызывающего кода, не полагаясь на два конкурирующих перегруженных метода со строковыми аргументами без меток.

Намерение вызывающего кода Предлагаемая точка входа Способ отображения
Передать переводимое сообщение с необязательным контекстом SummaryLabel(localized:) Text(resource)
Передать ключ SwiftUI, включая интерполяцию SwiftUI SummaryLabel(key:) Text(key, tableName:bundle:)
Показать метку в точности как передано SummaryLabel(verbatim:) Text(verbatim:)
Передать текст, уже составленный вызывающим кодом SummaryLabel(text:) Сохранить переданный Text

Оставьте LocalizedStringKey доступным, если компоненту специально нужно поведение SwiftUI на основе ключей. Apple указывает, что строковый литерал, переданный непосредственно в Text, создает ключ локализации, тогда как String переменная обычно использует путь в обход локализации. Поэтому оболочка, принимающая только String, меняет смысл вызовов, которые выглядят почти одинаково. LocalizedStringKey.

Создайте полный компонент с явными точками входа

Используйте проект приложения macOS SwiftUI в Xcode 26 или новее; для этого примера задайте целевую версию macOS 26. Замените сгенерированные объявления приложения и представления содержимого полной программой ниже; в целевом объекте оставьте только одно объявление @main. Все метки и количества — искусственные. Код проверен на соответствие документированным API, но здесь он не компилировался, поскольку в этой среде нет компилятора Swift.

import Foundation
import SwiftUI

struct SummaryLabel: View {
    private let text: Text

    init(localized resource: LocalizedStringResource) {
        text = Text(resource)
    }

    init(
        key: LocalizedStringKey,
        tableName: String? = nil,
        bundle: Bundle? = nil,
        comment: StaticString? = nil
    ) {
        text = Text(
            key,
            tableName: tableName,
            bundle: bundle,
            comment: comment
        )
    }

    init(verbatim value: String) {
        text = Text(verbatim: value)
    }

    init(text: Text) {
        self.text = text
    }

    var body: some View {
        text
            .font(.callout)
            .padding(8)
    }
}

struct ContentView: View {
    @State private var itemCount = 2
    private let collectionLabel = "Sample Set"

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Stepper(value: $itemCount, in: 0...12) {
                Text("Item count")
            }

            SummaryLabel(
                localized: LocalizedStringResource(
                    "summary.item-count",
                    defaultValue: "\(itemCount) items",
                    comment: "Summary below the item-count control. The integer is the number of items."
                )
            )

            SummaryLabel(
                key: "Collection: \(collectionLabel)"
            )

            SummaryLabel(verbatim: collectionLabel)

            SummaryLabel(
                text: Text(
                    "Preview",
                    comment: "Heading for a preview of the current item selection."
                ).bold()
            )
        }
        .padding(20)
        .frame(minWidth: 320)
    }
}

@main
struct TextContractDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

В инициализаторе ресурса стабильный ключ summary.item-count намеренно отделен от значения по умолчанию \(itemCount) items. Apple документирует для этого инициализатора статический ключ, локализуемое значение по умолчанию, таблицу, локаль, пакет и комментарий для переводчика. Значение по умолчанию используется, если ключ отсутствует в указанной таблице. Инициализатор LocalizedStringResource.

Инициализатор verbatim: дает столь же ясную гарантию: переданная строка отображается без локализации. Он работает и с литералом, и с переменной. Используйте этот путь для искусственной метки коллекции, если важно показать ее точное написание. Text.init(verbatim:).

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

Чем отличаются извлечение сообщений и форматирование во время выполнения?

Проверяйте локализацию в два этапа. При сборке Xcode находит в исходном коде локализуемые выражения и обновляет каталог строк. Во время выполнения приложение разрешает сообщение и подставляет аргументы. В описании Apple сессии по каталогам строк прямо говорится, что система распознает литералы, использованные для создания LocalizedStringResource в месте вызова пользовательского представления. Там также упоминается настройка сборки Use Compiler to Extract Swift Strings. Обнаружение каталогов строк.

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

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

import SwiftUI

let count = 2

// Ordinary String interpolation has already produced "2 items".
let completed: String = "\(count) items"
let exactDisplay = Text(completed)

// Looks up the completed value as a key.
// This does not restore a count placeholder.
let completedValueLookup = Text(LocalizedStringKey(completed))

// Preserves localization-aware interpolation from the beginning.
let messageKey: LocalizedStringKey = "\(count) items"
let interpolatedDisplay = Text(messageKey)

Создание LocalizedStringKey из переменной — документированный способ запросить поиск во время выполнения. Он полезен, если переменная намеренно содержит известный ключ. Однако это не гарантирует, что Xcode извлек в каталог все возможные значения этой переменной. Явно поддерживайте динамические ключи, которые действительно нужны, или используйте объявления ресурсов с литералами для конечного набора сообщений приложения. Поведение поиска и резервный вариант описаны в Text.init(_:tableName:bundle:comment:).

Сохраняйте все сообщение целиком, включая пунктуацию и расположение заполнителей, в одном локализуемом выражении. В этом примере оставьте Collection: \(collectionLabel) единым выражением. Сборка готового сообщения из отдельно разрешенных фрагментов возложила бы на вызывающий код ответственность за их порядок и интервалы.

Добавьте каталог и настройте формы множественного числа

Сначала добавьте каталог строк через File > New > File from Template, выберите String Catalog и назовите его Localizable. Затем выберите Product > Build. В документации Apple сказано, что при сборке каталог наполняется строками из локализуемых API и обновляется при изменении исходных сообщений. Локализация и изменение текста с помощью каталога строк.

Для примера найдите запись summary.item-count с интерполированным целым числом. Щелкните запись, удерживая Control, и выберите Vary by Plural. В исходной локализации на английском задайте для формы One значение %lld item, а для Other — %lld items, сохранив целочисленный заполнитель, добавленный Xcode. При добавлении других локализаций добавьте необходимые формы: категории множественного числа зависят от языка. Локализация и изменение текста с помощью каталога строк.

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

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

Проверка Ожидаемый результат
Собрать проект и проверить summary.item-count Одна запись сообщения с подстановкой целого числа
Установить количество 0 0 items
Установить количество 1 1 item
Установить количество 2 2 items
Проверить строку с точной меткой Sample Set остается буквальным текстом
Проверить сообщение коллекции Метка передается как аргумент внутри полного сообщения

Отдельно диагностируйте отсутствие записей и неправильное отображение

Если сообщения нет в каталоге, сначала проверьте исходный тип. Параметр String мог стереть локализуемое выражение до передачи в компонент. Проверьте целевой объект каталога, соберите схему, содержащую место вызова, и убедитесь, что включена настройка Use Compiler to Extract Swift Strings. В документации Apple сказано, что извлечение выполняется для текущей схемы и платформы, поэтому проверяйте конфигурацию, которая действительно компилирует это сообщение. Обнаружение каталогов строк.

Если запись есть, но приложение показывает ключ или резервный текст, проверьте настройки поиска. Инициализатор Text на основе ключа ищет строку в указанной таблице и пакете; если значения не указаны, используются таблица по умолчанию и основной пакет. Если ключа или таблицы нет, отображается строковое представление ключа. Поэтому читаемый резервный текст может скрывать неполную настройку поиска. Text.init(_:tableName:bundle:comment:).

При переносе компонента в фреймворк или пакет решите, где должны храниться сообщения. Для сообщений, принадлежащих компоненту, нужен пакет ресурсов компонента; ресурсы вызывающего кода должны сохранять переданные ему сведения для поиска. В актуальных рекомендациях Apple предлагается передавать #bundle в API локализации, чтобы указать пакет целевого объекта, в котором находится код. Именованным таблицам также нужны соответствующие каталоги. Подготовка текста приложения к переводу.

Наконец, не считайте, что все варианты интерполяции, поддерживаемые SwiftUI, подойдут и ресурсу Foundation. У LocalizedStringKey есть собственные методы интерполяции, включая варианты с форматной строкой и форматтером. Выбирайте тип сообщения до составления выражения и проверяйте поддерживаемые операции интерполяции, если аргумент не компилируется. LocalizedStringKey.StringInterpolation.

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

Источники