Ask the assistant
0/400
Your question is processed by an AI service and kept to improve answers. Do not enter personal data.

    Тема и локализация в REEEL ( $theme / $strings )

    This page has not been translated into English yet. The Russian original is below.

    REEEL даёт два ambient-объекта , доступных в любом файле проекта без объявления и без JsonData / import — ровно как Theme.of(context) / Localizations.of(context) во Flutter:

    • $theme — токены оформления (цвета, при необходимости — типографика и пр.);
    • $strings — строки локализации активного языка.

    Читаются они через доступ к членам по точке ( $theme.primary , $strings.welcome ), а действия (смена темы/языка) — через вызов методов-членов ( $theme.set("dark") , $strings.set("ru") , $strings.t("key", …) ).

    ColoredBox color:$theme.backgroundPage
      Center
        Column mainAxisAlignment:center spacing:16
          Text $strings.welcome
            #style color:$theme.textPrimary fontSize:28 fontWeight:w700
          Text {strings.t("greeting", name:"REEEL")}
            #style color:$theme.primary
          Row mainAxisAlignment:center spacing:8
            FilledButton onPressed:(){theme.set("dark")}
              Text $strings.themeDark
            TextButton onPressed:(){strings.set("ru")}
              Text "RU"
    Внутри {…} имена пишутся без $ (как у всех переменных в выражениях): {theme.primary} , {strings.t("k")} . В позиции значения — с $ : color:$theme.primary .

    $theme — тема и цвета

    $theme.<token> возвращает значение токена активного режима. Имена токенов — плоские ( primary , backgroundPage , backgroundSurface , textPrimary , textMuted , onPrimary , accent , …). Цвет приходит строкой ( #AARRGGBB ) и резолвится обычным механизмом цвета, поэтому слот цвета принимает $theme.primary или литерал #hex одинаково.

    Встроенные члены:

    Член Значение
    $theme.<token> значение токена (цвет/число/строка)
    $theme.mode текущий режим ( "light" / "dark" / своё имя схемы)
    $theme.set("dark") переключить режим (вызывается из колбэка)

    Откуда берутся токены (слои)

    Активная карта $theme собирается слоями, позже — важнее :

    1. База хоста — дефолтные токены (в редакторе — дизайн-токены редактора), чтобы экран без собственной темы выглядел осмысленно.
    2. Тема проекта — файлы data/theme/<mode>.json (см. ниже): переопределяют только заданные в них токены; остальное берётся из базы.
    // data/theme/dark.json — переопределяет часть токенов
    { "backgroundPage": "#FF1A1110", "textPrimary": "#FFF5EDE6", "primary": "#FFD9A066" }

    Переключение

    onPressed:(){ theme.set("light") } — сменить режим. Выбор сохраняется (Storage) и восстанавливается при следующем запуске; одно значение синхронно во всех файлах.

    Системная тема ( system )

    По умолчанию выбор темы — system : без своего переключателя проект следует за светлой/тёмной настройкой ОС ( $theme.mode == "system" , эффективная яркость — $theme.brightness ). theme.set("dark") / "light" фиксирует режим, theme.set("system") возвращает следование за ОС. В редакторе превью симулирует системную настройку тогглом ☀️/☾ рядом с тогглом ориентации; на устройстве берётся реальная platformBrightness .


    $strings — локализация

    $strings.<key> — строка активного языка. С аргументами/плюралом — $strings.t(...) .

    Член Значение
    $strings.<key> строка по ключу (или сам ключ, если не найдена)
    $strings.locale текущая локаль ( "en" / "ru" / …)
    $strings.set("ru") сменить язык (из колбэка)
    $strings.t("key", name:…, 0:…) строка + подстановка {name} / {0}
    Text $strings.welcome                              // простой ключ
    Text {strings.t("greeting", name:"REEEL")}         // подстановка {name}
    Text {strings.t("items", "5")}                     // подстановка {0}
    Text {plural($n, one:$strings.itemOne, other:$strings.itemMany)}  // плюрал

    Языки обнаруживаются в runtime из data/strings/ проекта (никакого хардкода списка локалей). Выбор локали сохраняется в Storage и синхронен между файлами.

    Системный язык ( system )

    По умолчанию выбор локали — system : без своего переключателя проект берёт язык по приоритету предпочитаемых локалей ОС среди доступных ( $strings.locale == "system" , эффективная — $strings.effectiveLocale ); если ни одна не подходит — фолбэк locales.default из project.json , иначе первая доступная. strings.set("ru") фиксирует язык, strings.set("system") возвращает следование за системой. В редакторе превью симулирует системный язык селектором рядом с тогглом темы; на устройстве берётся реальная локаль ОС.

    Системный язык на вебе

    Правило то же, меняется только его форма: у браузера нет «локалей ОС», он присылает их заголовком Accept-Language с весами q . Сервер выбирает лучшую из доступных по этому приоритету; не подошла ни одна — locales.default конфигурации, иначе первая доступная.

    Отличий от устройства два, и оба следуют из того, что у страницы есть адрес.

    Выбор живёт в URL, а не в Storage. Каждая локаль имеет свой префикс, включая локаль по умолчанию: /en/about , /ru/about . Префиксованный адрес — это уже сделанный выбор, и согласование его не перебивает. strings.set("ru") уводит на /ru/… , и все ссылки, которые страница порождает, остаются внутри её локали. Поэтому согласование происходит один раз , на входе: первый же клик закрепляет посетителя на явном адресе.

    Непрефиксованный адрес — точка входа. /about — не английская страница, а та, которая выбирает язык по заголовку. Она же объявлена как x-default , а canonical ведёт на однозначный /<locale>/about . Ответ несёт Content-Language и Vary: Accept-Language , а его ETag включает локаль — иначе общий кэш отдал бы одному посетителю страницу на языке другого.

    Статический экспорт согласовывать не может: сервера нет, читать заголовок некому. Он раскладывает по файлу на локаль ( /en/about , /ru/about ) и кладёт умолчание ещё и в корень. Клиентского редиректа по navigator.language в экспорте нет намеренно — клиентской локализации в REEEL нет вообще, и заводить её в одном месте значило бы развести экспорт со всеми остальными способами отдачи.


    Файлы проекта: data/theme/ и data/strings/

    Темы и переводы живут под каталогом data/ проекта (рядом с data/<...>.json для JsonData ):

    <project>/
      data/
        theme/
          light.json        # токены светлой темы  → $theme.* (mode "light")
          dark.json         # токены тёмной темы   → $theme.* (mode "dark")
        strings/
          en.arb            # строки en            → $strings.* (locale "en")
          ru.arb            # строки ru            → $strings.* (locale "ru")
      reeel/
        main.reeel
      project.json

    Правила обнаружения (выполняются хостом/превью при открытии проекта):

    • Тема: каждый data/theme/*.json → режим по имени файла ( dark.json → dark ).
    • Локали: каждый data/strings/*.arb или *.json → локаль по имени файла ( en.arb → en , app_ru.json → ru ; берётся часть после последнего _ , иначе всё имя).
    • ARB — это JSON; служебные ключи ARB ( @@locale , @key ) безвредны (не конфликтуют с обычными ключами).

    Формат значений:

    // data/theme/light.json — токен → цвет (#AARRGGBB или #RRGGBB)
    { "backgroundPage": "#FFFDF6EC", "textPrimary": "#FF2A1A12", "primary": "#FFB5651D" }
    // data/strings/en.arb — ключ → строка (с плейсхолдерами {name} / {0})
    { "welcome": "Hello, REEEL", "greeting": "Welcome, {name}" }

    Реактивность

    $theme / $strings реактивны: theme.set(...) / strings.set(...) (или загрузка файлов) обновляют ambient-значение, и все элементы, читающие $theme.* / $strings.* , перестраиваются автоматически — через тот же механизм подписки, что и обычные @ -переменные.

    Лучшие практики

    • Цвета — токенами, не hex: color:$theme.primary , не color:#9B8AFB . Так экран следует теме проекта/бренда и переключается.
    • Строки — ключами, не литералами: Text $strings.welcome , не Text "Welcome" .
    • Подстраховка ?? : dotted-чтение неизвестного члена даёт null ; в выражении прикрывайте — {theme.primary ?? "#000000"} , {strings.title ?? "—"} . ( Text $strings.x при отсутствии строки деградирует в пустую строку, не падает.)
    • Не объявляйте @theme / @strings своими переменными — затенят ambient.

    См. также

    DOCS
    dark_mode_baseline
    RU/EN
    menu_baseline