Skip to content

Tutorial 5 Localization ru

James Morris edited this page Jul 29, 2026 · 1 revision

Урок 5 · Локализация (i18n)

Цель: понять, как LockedIn CLI говорит на 33 языках — и потренироваться направлять агента добавить ещё один. Локализация — фантастическая задача для агента: она достаточно механическая, чтобы делегировать, но у неё есть настоящие ограничения (тестовый барьер, правила раскладки, грамматическое ревью), которые учат вас проверять.

← Назад: Урок 4 Промптинг и ревью · Обратно к Главная


Что здесь значит «локализовано»

Запустите CLI на испанском, хинди, японском, упрощённом китайском или любом поставляемом языке, и всё меняется — заставка, таблица справки, вывод каждой команды, сессия чата, даже юридический мелкий шрифт. Не только шутки: вся видимая поверхность.

lockedin --lang ru post
LOCKEDIN_LANG=hi lockedin
lockedin --lang zh aura

Язык автоопределяется при запуске, разрешается в порядке приоритета:

  1. флаг --lang (--lang ru, --lang=fr, -l ja)
  2. переменная окружения LOCKEDIN_LANG
  3. ваша локаль (LC_ALL / LC_MESSAGES / LANG, затем локаль ОС/среды выполнения)
  4. английский, как запасной вариант

normalizeLang() обычно использует основной subtag локали. Это значит, что de-DE выбирает de, но tlh не становится случайно tl; настоящие алиасы fil и tgl намеренно отображаются в тагальский (tl), норвежские nb и nn отображаются в no, устаревший индонезийский in отображается в id, а устаревший иврит iw отображается в he. Два региональных кода сохраняются дословно, а не сворачиваются к основному subtag: pt-BR/pt_BR выбирают канонический региональный код, тогда как общий pt остаётся обратно совместимым бандлом бразильского португальского (оба — бразильский португальский и разделяют одни pools/UI), а en-SG/en_SG сохраняют синглиш (общий en остаётся английским). Гонконгский традиционный китайский — то же исключение: zh-HK, zh_HK.UTF-8 и zh-Hant-HK выбирают zh-HK, тогда как общий zh и материковые теги выбирают упрощённый китайский (zh).

Переключение на лету — панель /language. CLI всегда мог стартовать на другом языке (--lang, LOCKEDIN_LANG); теперь вы можете переключаться посреди сессии. Напечатайте /language (алиасы /lang и /languages), чтобы перечислить все 33 языка по коду, каждый в своём письме; /language ru переключает на остаток сессии. Смысл — в аварийном выходе: после переключения он перерисовывает на новом языке, а затем, на языке, который вы только что покинули, печатает точный путь обратно — /language en сейчас, lockedin --lang en в следующий раз — чтобы случайное приземление в 日本語 или ಕನ್ನಡ никогда вас не заперло. (Переключитесь дважды, и предлагается также язык, который называет ваш LOCKEDIN_LANG.) --lang и LOCKEDIN_LANG без изменений. Как и /a11y, это искренняя утилита, а не часть сатиры.

Идея: языковые бандлы

Весь переводимый текст живёт в бандлах, по одному на язык, каждый в форме:

{ meta: { lang: 'ru', name: 'Русский', dir: 'ltr' },
  pools: { HOOKS: [ /* ~25 */ ], LESSONS: [ /* ... */ ], /* ... */ },
  ui:    { buzzwordDensity: 'Плотность buzzwords: ', /* labels, headings */ } }
  • pools — это массивы контента (шутки), которые вы встретили в Главе 2.
  • ui — это строки «обвязки»: метки, заголовки и небольшие шаблоны.

Английский — это эталонный бандл внутри src/lockedin.js; другие 32 модуля бандлов живут в src/content/*.js: ar, bn, bo, de, el, en-SG, es, eu, fa, fi, fr, he, hi, id, is, it, ja, kn, ms, nl, no, pl, pt, pt-BR, ru, sv, tl, tr, uk, ur, zh и zh-HK (pt-BR переиспользует pools/UI из pt, но регистрируется отдельно). Каждый зарегистрирован в BUNDLES; SUPPORTED_LANGS генерируется из этих ключей, и renderHelp() печатает этот сгенерированный список кодов. Ни один UI-бандл не хардкодит список.

setLang('fr');       // указать активный язык на французский бандл
// L = активные pools, U = активный ui
pick(L.HOOKS)        // французский hook
U.buzzwordDensity    // "Densité de jargon : "

Поскольку каждый рендерер читает L и U (никогда захардкоженную строку), один setLang меняет весь опыт. В этом весь фокус.

Страховочная сетка: паритет ключей

Вот инвариант, который делает добавление языка безопасным:

Каждый бандл должен предоставлять точно те же ключи pools и ui, что и английский.

Тест обеспечивает это по всем 33 бандлам. Если вы добавите новую UI-строку в английский и забудете перевести её на украинский, npm test станет красным и скажет вам, какой ключ отсутствует. Вы не можете молча выкатить наполовину переведённый язык.

Трудная часть: раскладка терминала

Языки нагружают раскладку терминала по-разному:

  • Японский, упрощённый китайский и гонконгский традиционный китайский используют символы East Asian Wide / Fullwidth. vw() считает их за два столбца, а wrap() жёстко переносит длинные токены без пробелов, чтобы текст CJK оставался внутри карточек и рамок.
  • Хинди и каннада используют непробельные/охватывающие комбинируемые знаки (Mn / Me), такие как matra и virama. vw() считает их за ноль столбцов, чтобы они не раздували измеренную ширину.
  • box() переносит каждую строку тела перед добавлением отступов, так что длинный переведённый баннер больше не может пробить рамку.
  • Каждый бандл задаёт sentenceEnd и listSep (например . / , , / ), чтобы составленные генератором предложения читались естественно.

Когда вы добавляете язык, строки заголовка карточки (cardSubtitle, cardMeta, cardFooter) всё равно должны помещаться в ≤ 60 видимых столбцов. Арабский, персидский, иврит и урду задают meta.dir: 'rtl'. Вывод не содержит bidi-управляющих символов по умолчанию, потому что некоторые терминалы рендерят их как метки в рамках. LOCKEDIN_BIDI=on явно включает сбалансированные isolates после переноса для терминалов, о которых известно, что они их поддерживают, сохраняя ANSI, ASCII-команды и логический порядок копирования/вставки. Доступный вывод всегда убирает эти управляющие символы. Без явного включения смешанный порядок RTL/LTR может быть менее изощрённым; никогда не зондируйте и не выводите поддержку.

Коварная часть: грамматика вокруг сырого ввода пользователя

Некоторые UI-шаблоны вставляют сырые пользовательские фразы с плейсхолдерами вроде {cap}. Не переводите их слот-в-слот. Предложение должно оставаться грамматичным, когда плейсхолдер — это фраза, которую напечатал пользователь, а не аккуратное существительное.

Реальный предостерегающий баг: японские шаблоны, которые ставят сразу после {cap}, могут звучать неправильно, когда {cap} — это полное предложение. Исправление не «переводить усерднее»; это перестроить шаблон (например, добавить номинализатор или переместить плейсхолдер), чтобы произвольный пользовательский ввод всё ещё подходил.

✅ Попробуйте со своим агентом — добавьте язык

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

Добавьте датский (da). Создайте src/content/da.js как бандл { meta, pools, ui } с теми же ключами, что и английский, переведя каждую запись (пулы контента по ~25 каждый, все UI-строки). Зарегистрируйте da в BUNDLES в src/lockedin.js. --lang da и локаль da-* должны его выбирать. Держите строки заголовка карточки в пределах лимита ширины. npm test должен оставаться зелёным, и добавьте инварианты для датского + тесты обнаружения, отражающие существующие локализованные.

Затем прогоните цикл из Глав 3–4:

  1. Сначала план. «Прежде чем писать код, скажи мне файлы, которые ты изменишь, и как ты сохранишь паритет ключей с английским.»
  2. Сначала тесты. «Добавь падающие тесты: обнаружение da, паритет ключей для da, и датский инвариант reflect/connect. Пока не создавай бандл.»
  3. Реализация. «Теперь создай src/content/da.js, переводя существующий бандл ключ за ключом, зарегистрируй его и сделай так, чтобы тесты прошли. Только pick/shuffle для случайности.»
  4. Барьер + ревью. npm test, затем lockedin --lang da post — и прочитайте diff: переведён ли каждый ключ? Всё ещё выравниваются ли рамки карточек? Выживают ли шаблоны с {cap} на сырых пользовательских фразах?

Меньшие разминочные упражнения, если целый язык — это слишком:

  • «Добавь ещё один TAGLINE во все 33 языковых бандла, сохраняя количество равным.»
  • «Проверь, ≤ 60 ли видимых столбцов у cardFooter каннада, и объясни, как измерялись комбинируемые знаки.»
  • «Покажи мне тест, который упал бы, если бы я удалил ключ ui из ja.js

Куда идти дальше

  • Пробегитесь по src/content/es.js — он всё ещё дружелюбный шаблон для нового бандла.
  • Перечитайте docs/HANDOFF.md → «Adding a language».
  • Насладитесь многоязычными шутками в Справочнике команд.

Это полный учебник. Вы теперь умеете направлять ИИ-агента строить функции и локализовать их за тестовым барьером — на 33 языках и готовые к большему. Согласны? 👇

📘 LockedIn CLI wiki

Tutorial

Reference


Satire · Sátira · 風刺. Not affiliated with LinkedIn. GPL-3.0-or-later.

Clone this wiki locally