Интернационализация и локализация для статических сайтов

Опубликовано: 2022-03-10
Краткое резюме ↬ Интернационализация и локализация — это больше, чем просто написание контента на нескольких языках. Вам нужна стратегия, чтобы определить, какую локализацию отправить, и код для этого. Вы должны иметь возможность поддерживать не только разные языки, но и разные регионы с одним и тем же языком. Ваш пользовательский интерфейс должен реагировать не только на размер экрана, но и на разные языки и режимы письма. Ваш контент должен быть структурирован, вплоть до микротекста в вашем пользовательском интерфейсе и формата ваших дат, чтобы его можно было адаптировать к любому языку, который вы используете. Выполнение всего этого с помощью генератора статических сайтов, такого как Eleventy, может сделать это еще сложнее, потому что у вас может не быть базы данных, тем не менее, сервера. Все это можно сделать, но требует планирования.

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

Создавая chromeOS.dev, мы знали, что нам нужно сделать его доступным для глобальной аудитории. Убедиться, что наша кодовая база может поддерживать несколько локалей (язык, регион или их комбинацию) без необходимости кодирования каждого из них, позволяя при этом выполнять перевод с минимальным знанием этой системы, было бы критически важно для создания это случилось. Наши создатели контента должны были иметь возможность сосредоточиться на создании контента, а наши переводчики — на его переводе, при этом прилагая как можно меньше усилий для размещения своей работы на сайте и развертывания. Удовлетворение этих иногда противоречащих друг другу потребностей является сердцевиной того, что требуется для интернационализации кодовых баз и локализации сайтов.

Интернационализация (i18n) и локализация (l10n) — две стороны одной медали. Интернационализация заключается в том, как, в нашем случае, программное обеспечение разрабатывается таким образом, чтобы его можно было адаптировать для нескольких языков и регионов без необходимости внесения инженерных изменений. Локализация, с другой стороны, заключается в фактической адаптации программного обеспечения для этих языков и регионов. Интернационализация может происходить во всем стеке веб-сайта; от HTML, CSS и JS до проектирования и создания систем. Локализация происходит в основном при создании контента (как полного, так и микрокопии) и управлении им.

Примечание . Для любопытных: i18n и l10n — это типы сокращений, известные как нумеронимы. A11y для доступности — еще один распространенный нумероним в веб-разработке.

Еще после прыжка! Продолжить чтение ниже ↓

Интернационализация (i18n)

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

Определение языка и региона пользователя

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

Как правило, существует три высокоуровневых способа определить, какую локализацию предоставлять пользователям:

  1. Местоположение по IP-адресу;
  2. Заголовок Accept-Language или navigator.languages ​​;
  3. Идентификатор в URL.

Многие системы объединяют одну, две или все три, когда решают, какую локализацию обслуживать. Однако в ходе расследования мы обнаружили проблемы с использованием IP-адресов и заголовков Accept-Language , которые, по нашему мнению, были достаточно значительными, чтобы исключить их из рассмотрения:

  • Предпочтительный язык пользователя часто не соответствует его физическому местонахождению, указанному в IP-адресе. Тот факт, что кто-то физически находится, например, в Америке, не означает, что он предпочтет контент на английском языке.
  • Анализ местоположения по IP-адресам сложен, как правило, ненадежен и может помешать сканированию сайта поисковыми системами.
  • Заголовки Accept-Language часто никогда не устанавливаются явно и предоставляют информацию только о языке, а не о регионе. Из-за своих ограничений это может быть полезно для установления первоначального предположения о языке, но не обязательно надежно.

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

Существует три общих шаблона для встраивания идентификаторов в URL-адреса:

  1. Предоставьте разные домены (обычно TLD или поддомены для разных регионов и языков (например, example.com и example.de , en.example.org и de.example.org );
  2. Иметь локализованные подкаталоги для контента (например, example.com/en и example.com/de );
  3. Предоставляйте локализованный контент на основе параметров URL (например, example.com?loc=en и example.com?loc=de ).

Несмотря на широкое использование, параметры URL, как правило, не рекомендуются, поскольку пользователям сложно распознать локализацию (наряду с рядом проблем с аналитикой и управлением). Мы также решили, что разные домены не являются для нас хорошим решением; наш сайт представляет собой прогрессивное веб-приложение, и каждый домен, включая TLD и поддомены, считается источником другого происхождения, что фактически требует отдельного PWA для каждой локализации.

Мы решили использовать подкаталоги, что дало нам возможность локализовать только язык ( example.com/en ) или язык и регион ( example.com/en-US и example.com/en-GB ) по мере необходимости. поддержание единого PWA. Мы также решили, что каждая локализация нашего сайта будет располагаться в подкаталоге, чтобы один язык не возвышался над другим, и что все URL-адреса, за исключением подкаталога, будут идентичны в разных локализациях на основе языка автора, что позволит пользователям легко изменять локализации без необходимости переводить URL-адреса.

Обслуживание локализованного контента

После того, как стратегия определения языка и региона пользователя определена, вам нужен способ надежно предоставлять им нужный контент. Как минимум, для этого потребуется некоторая форма хранимой информации, будь то файл cookie, локальное хранилище или часть пользовательской логики вашего приложения. Возможность сохранять пользовательские настройки локализации — важная часть пользовательского опыта i18n; если пользователь определил, что ему нужен контент на немецком языке, и он попадает на английский контент, вы сможете определить его предпочтительный язык и соответствующим образом перенаправить его. Это можно сделать на сервере, но решение, которое мы выбрали для chromeOS.dev, не зависит от хостинга и настройки сервера: мы использовали сервис-воркеры. Путь пользователя выглядит следующим образом:

  • Пользователь впервые заходит на наш сайт. Наш сервис-воркер не установлен.
  • Какую бы локализацию они ни выбрали, мы устанавливаем их в качестве предпочтительного языка в IndexedDB. Для этого мы предполагаем, что они попадают туда с помощью каких-то средств, социальных, реферальных или поисковых, которые направили их на основе других контекстов локализации, которых у нас нет. Если пользователь попадает без набора локализации, мы устанавливаем его на английский, так как это основной язык нашего сайта. У нас также есть переключатель языка в нижнем колонтитуле, чтобы пользователь мог изменить свой язык. На этом этапе наш сервис-воркер должен быть установлен.
  • После установки сервис-воркера мы перехватываем все URL-запросы для навигации по сайту. Поскольку наши локализации основаны на подкаталогах, мы можем легко определить, какая локализация запрашивается. После идентификации мы проверяем, находится ли запрошенная страница в локализованном подкаталоге, проверяем, находится ли локализованный подкаталог в списке поддерживаемых локализаций, и проверяем, соответствует ли локализованный подкаталог их предпочтениям, хранящимся в IndexedDB. Если ее нет в локализованном подкаталоге или локализованный подкаталог соответствует их предпочтениям, мы обслуживаем страницу; в противном случае мы делаем перенаправление 302 от нашего сервис-воркера для правильной локализации.

Мы включили наше решение в плагин Workbox, Service Worker Internationalization Redirect. Плагин вместе с подмодулем настроек можно комбинировать для установки и получения языковых предпочтений пользователя и управления перенаправлением в сочетании с методом registerRoute Workbox и фильтрацией запросов в request.mode === 'navigate' .

Полный минимальный пример выглядит так:

Код клиента

 import { preferences } from 'service-worker-i18n-redirect/preferences'; window.addEventListener('DOMContentLoaded', async () => { const language = await preferences.get('lang'); if (language === undefined) { preferences.set('lang', lang.value); // Language determined from localization user landed on } });

Код сервисного работника

 import { StaleWhileRevalidate } from 'workbox-strategies'; import { CacheableResponsePlugin } from 'workbox-cacheable-response'; import { i18nHandler } from 'service-worker-i18n-redirect'; import { preferences } from 'service-worker-i18n-redirect/preferences'; import { registerRoute } from 'workbox-routing'; // Create a caching strategy const htmlCachingStrategy = new StaleWhileRevalidate({ cacheName: 'pages-cache', plugins: [ new CacheableResponsePlugin({ statuses: [200], }), ], }); // Array of supported localizations const languages = ['en', 'es', 'fr', 'de', 'ko']; // Use it for navigations registerRoute( ({ request }) => request.mode === 'navigate', i18nHandler(languages, preferences, htmlCachingStrategy), );

Благодаря сочетанию клиентского кода и кода сервисного работника предпочтительная локализация пользователей будет автоматически установлена ​​при первом посещении сайта, и если они перейдут по URL-адресу, который не находится в их предпочтительных локализациях, они будут перенаправлен.

Адаптация пользовательского интерфейса сайта

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

Котировки блоков

Распространенным шаблоном проектирования является заключение цитат в кавычки, но знаете ли вы, что используется для этих кавычек, зависит от локализации? Вместо жесткого кодирования используйте open-quote и закрытые close-quote чтобы убедиться, что правильные кавычки используются для правильного языка.

Блок-цитата из руководства по стилю, используя open-quote, close-quote для кавычек в начале и конце, на странице с lang="en"
open-quote и close-quote для lang=“en” отображаются как две надстрочные запятые, направленные внутрь текста, причем первая пара перевернута. (Большой превью)
Блок-цитата из нашего руководства по стилю, используя open-quote, close-quote для кавычек в начале и конце, на странице с lang="fr"
open-quote и close-quote для lang=“fr” отображаются в виде пары шевронов, отверстия которых обращены внутрь к тексту. (Большой превью)

Формат даты и числа

И у дат, и у чисел есть метод .toLocaleString , позволяющий форматировать на основе локализации (язык и/или регион). Браузеры, поддерживающие их, поставляются со всеми доступными локализациями, что делает их удобными для использования там, но Node.js — нет. К счастью, модуль full-icu для Node позволяет использовать все доступные данные локализации. Для этого после установки модуля запустите свой код с переменной среды NODE_ICU_DATA , для которой задан путь к модулю, например, NODE_ICU_DATA=node_modules/full-icu .

Метаинформация HTML

В вашем HTML-теге и заголовках есть три области, которые следует обновлять при каждой локализации:

  • Язык страницы,
  • Направление письма,
  • Альтернативные языки, на которых доступна страница.

Первым переходит к элементу html со свойствами dir и lang соответственно, например, <html lang="en" dir-"ltr"> для американского английского. Их правильная настройка обеспечит поток контента в правильном направлении и позволит браузерам понять, на каком языке находится страница, что позволит использовать дополнительные функции, такие как перевод контента. Вы также должны включить ссылки rel="alternate" , чтобы поисковые системы знали, что страница была полностью переведена, поэтому добавление <link href="/es" rel="alternate" hreflang="es"> на нашу англоязычную целевую страницу будет пусть поисковые системы знают, что у этого есть перевод, который следует искать.

Внутренний дизайн

Локализация контента может вызвать проблемы с дизайном, поскольку разные переводы будут занимать разное количество места на странице. В некоторых языках, например в немецком, есть более длинные слова, требующие большего горизонтального пространства или более щадящего переноса текста. Другие языки, такие как арабский, имеют более высокие шрифты, требующие большего вертикального пространства. К счастью, существует ряд CSS-инструментов, позволяющих сделать интервалы и макет чувствительными не только к размеру области просмотра, но и к содержимому, что означает, что они лучше адаптируются к нескольким языкам.

Существует ряд модулей CSS, специально предназначенных для работы с контентом. Существуют единицы em и rem , представляющие рассчитанный размер шрифта и размер корневого шрифта соответственно. Замена значений px фиксированного размера для этих единиц может иметь большое значение для того, чтобы сделать сайт более отзывчивым к своему контенту. Затем есть единица ch , представляющая встроенный размер 0 (нулевого) глифа в шрифте. Это позволяет вам связать такие вещи, как width , например, непосредственно с содержимым, которое оно содержит.

Затем эти блоки можно комбинировать с существующими мощными инструментами CSS для макета, в частности, с flexbox и сеткой, для компонентов, которые адаптируются к их размеру, а макеты адаптируются к их содержимому. Улучшение их логическими свойствами для границ, полей и отступов вместо физических физических свойств позволяет этим макетам и компонентам автоматически адаптироваться к режиму письма. Сила внутреннего веб-дизайна (придуманного Джен Симмонс, единиц измерения с учетом содержимого и логических свойств) позволяет проектировать и создавать интерфейсы, которые можно адаптировать к любому языку, а не только к любому размеру экрана.

Локализация (l10n)

Наиболее очевидная форма локализации — это перевод контента с одного языка на другой. В более тонких формах перевод происходит не только по языку, но и по региону, на котором говорят, например, английский, на котором говорят в Америке, по сравнению с английским, на котором говорят в Соединенном Королевстве, Южной Африке или Австралии. Чтобы добиться успеха здесь, важно понимать, что переводить и как структурировать контент для перевода.

Контент-стратегия

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

Контент-стратегия имеет множество определений, но здесь имеется в виду структура контента, микротекст (слова и фразы, используемые на протяжении всего проекта, не привязанные к конкретному фрагменту контента) и их связи. Для получения более подробной информации о контент-стратегии я бы порекомендовал «Стратегия контента для мобильных устройств» Карен МакГрейн и «Проектирование подключенного контента» Кэрри Хейн и Майка Атертона.

Для chromeOS.dev мы завершили кодирование моделей контента, описывающих структуру нашего контента. Модели контента предназначены не только для длинного контента, похожего на статью; модель контента должна существовать для любой сущности, которую пользователь может конкретно захотеть от вас, например автора, документа или даже повторно используемых медиаресурсов. Хорошие модели контента включают индивидуально адресуемые фрагменты или фрагменты более крупного концептуального фрагмента, исключая при этом фрагменты, которые косвенно связаны или на которые можно ссылаться из другой модели контента. Например, модель контента для поста в блоге может включать заголовок, массив тегов, ссылку на автора, дату публикации и текст поста, но она не должна включать строку для навигационных крошек или имя автора и изображение, которое должно быть собственной моделью контента. Модели контента не меняются от локализации к локализации; они являются структурой сайта. Экземпляр модели контента привязан к локализации, и эти экземпляры могут быть локализованы.

Однако модели контента охватывают только часть того, что необходимо локализовать. Остальное — кнопки «Подробнее», заголовок «Меню», текст отказа от ответственности — все это микрокопия. Микрокопия тоже нуждается в структуре. В то время как модели контента могут казаться естественными для создания, особенно для сайтов, управляемых шаблонами, модели микрокопий, как правило, менее очевидны и часто случайно упускаются из виду, записывая то, что необходимо непосредственно в шаблоне.

Создавая модели контента и микрокопий и применяя их — с помощью системы управления контентом, анализа или проверки — вы можете гарантировать, что локализация может сосредоточиться на локализации.

Локализуйте значения, а не ключи

Модели контента и микрокопии обычно создают структуры, похожие на объекты в кодовой базе; будь то записи базы данных, объект JSON, YAML или Front Matter. Не локализуйте ключи объектов! Если у вас есть микрокопия текста поиска, расположенная в объекте microcopy по адресу microcopy.search.text , не помещайте ее в объект microcopie по адресу microcopie.chercher.texte . Ключи в модулях следует рассматривать как идентификаторы, не зависящие от локализации, чтобы их можно было надежно использовать в повторно используемых шаблонах и полагаться на них во всей кодовой базе. Это также означает, что ключи объекта не должны отображаться для конечных пользователей в виде содержимого или микрокопии.

Настройка статического сайта

Для chromeOS.dev мы использовали Eleventy (11ty) с Nunjucks в качестве генератора статических сайтов, но эти рекомендации по настройке генератора статических сайтов должны быть применимы к большинству генераторов статических сайтов. Где что-то 11ти конкретное, то оно и будет названо.

Структура папок

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

 . └── pages ├── _data ├── _generated └── {{locale-code}} ├── {{locale-code}}.11tydata.js ├── _data └── [...content]

На верхнем уровне есть каталог для хранения страниц сайта, который здесь называется pages . Внутри находится папка _data , содержащая глобальные файлы данных. Эта папка важна, когда мы говорим о хелперах. Затем есть папка _generated . У нас есть несколько страниц, которые не имеют собственного контента, а создаются из существующего контента, небольшого количества микрокопий или комбинации того и другого. Думайте о домашней странице, странице поиска или целевой странице раздела блога. Поскольку эти страницы сильно шаблонизированы, мы храним шаблоны в папке _generated и создаем их оттуда вместо того, чтобы иметь для каждой отдельные файлы HTML или Markdown. Эти папки имеют префикс подчеркивания, чтобы указать, что они не выводят страницы непосредственно под ними, а скорее используются для создания страниц в другом месте.

Далее l10n подкаталогов! Каждый каталог должен быть назван в соответствии с языковым тегом BCP47 (чаще, кодом локали) для содержащейся в нем локализации: например, en для английского или en-US для американского английского. В кодовой базе chromeOS.dev мы также часто называем их локалями . Эти папки станут подкаталогами локализации, сегментируя контент по локализации. Каскад данных 11ty позволяет сделать данные доступными для каждого файла в каталоге и его дочерних элементов, если файл находится в корне каталога и называется так же, как каталог (так называемые файлы данных каталога). 11ty использует объект, возвращенный из этого файла, или функцию, которая возвращает объект и внедряет его в переменные, доступные для шаблонов, поэтому у нас есть доступ к данным здесь для всего контента этой локализации.

Чтобы упростить обслуживание этих файлов, мы написали помощник под названием l10n-data , часть нашего статического каркаса сайта, который использует эту структуру папок для создания каскада локализованных данных, позволяя локализовать данные по частям. Это достигается за счет того, что данные хранятся в каталоге данных для конкретной локали, каталоге _data в нем (загружаемом в файл данных каталога). Например, если вы заглянете в наш каталог данных английской локали, вы увидите модели микрокопий, такие как locale.json , который определяет код языка и направление письма, которые затем будут преобразованы в наш HTML, newsletter.yml , который определяет микрокопию, необходимую для подписка на информационный бюллетень и файл microcopy.yml , который включает общую микрокопию, используемую в нескольких местах на сайте, которая не помещается в более конкретный файл. Везде, где используется любая из этих микрокопий, мы извлекаем ее из этих данных, доступных через 11ty, вводя переменные данных в наши шаблоны для использования.

С микрокопией, как правило, труднее всего справиться, в то время как остальная часть контента в основном проста. Поместите свой контент, часто файлы Markdown или HTML, в локализованную подпапку. Для генераторов статических сайтов, которые работают со структурой папок, имя файла и структура папок контента обычно сопоставляются 1:1 с конечным URL-адресом для этого контента, поэтому файл Markdown в en/web/pwas.md будет выводиться в URL-адрес. en/web/pwa . Следуя нашему принципу локализации «значения, а не ключи», мы решили, что не будем локализовать имена файлов контента (и, следовательно, пути), чтобы нам было проще отслеживать статус локализации одного и того же файла в разных локалях, а пользователям было проще узнать они находятся на правильной странице между разными локалями.

I18n Помощники

В дополнение к содержимому и микротексту мы обнаружили, что нам необходимо написать ряд вспомогательных модулей, чтобы упростить работу с локализованным содержимым. В 11ty есть концепция, называемая фильтром, которая позволяет изменять содержимое перед его отображением. В итоге мы создали четыре из них, чтобы помочь с шаблонами i18n.

Первый — фильтр даты. Мы стандартизировали, что все даты в нашем контенте записываются в виде значения даты YAML, потому что мы в основном пишем их в YAML, и они становятся доступными в наших шаблонах как полная временная метка UTC. При использовании модуля и конфигурации full-icu строка даты (содержимое изменяется) вместе с кодом локали для отображаемого содержимого может быть передана непосредственно в Date.toLocaleString (с дополнительными параметрами форматирования) для отображения локализованной даты. Date.toLocaleDateString можно при желании использовать вместо этого, если вам просто нужна часть даты, когда параметры форматирования не передаются, вместо полной локализованной даты и времени.

Второй фильтр мы назвали localURL . Это берет локальный URL-адрес (содержимое изменяется) и языковой стандарт, в котором должен находиться URL-адрес, и меняет их местами. Он заменяет, например, /en/linux на /es/linux .

Последние два фильтра предназначены для извлечения локализованной информации только из кода локали. Третий использует модуль iso-639-10 для преобразования кода локали в название языка на родном языке. Это мы используем в первую очередь для нашего селектора языка. Четвертый использует модуль iso-i18n-countries для получения списка стран на этом языке. Это мы используем в основном для создания форм со списками стран.

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

Нашим последним и наиболее важным помощником были глобальные данные нашего сайта. Опираясь на структуру подкаталогов на основе кода локали, эта функция динамически определяет, какие локализации поддерживает сайт. Он создает глобальную переменную site , которая включает свойство l10n , содержащее всю микрокопию и специфичный для локализации контент из {{locale-code}}.11tydata.js . Он также содержит свойство languages , в котором перечислены все доступные локали в виде массива. Наконец, функция выводит файл JavaScript с подробным описанием языков, поддерживаемых сайтом, и отдельные файлы для каждой записи в {{locale-code}}.11tydata.js с ключом для каждой локализации, все они предназначены для импорта сценариями нашего браузера. Тяжелая работа этого файла связывает наш статический сайт с нашим внешним JavaScript, а единственным источником правды является информация о локализации, которая нам уже нужна. Это также позволяет нам программно генерировать страницы на основе наших локализаций, перебирая site.l10n в цикле. Это, в сочетании с нашими коллекциями, специфичными для локализации, позволяет нам использовать разбиение на страницы 11ty для создания локализованных домашних и новостных целевых страниц без создания отдельных HTML-страниц для каждой из них.

Заключение

Правильная интернационализация и локализация могут быть трудными; понимание того, как разные стратегии влияют на сложность, имеет решающее значение для упрощения процесса. Выберите стратегию i18n, которая естественным образом подходит для статических сайтов и подкаталогов, а затем создайте инструменты для автоматизации частей i18n и i10n из создаваемого контента. Создавайте надежные модели контента и микрокопий. Используйте сервис-воркеры для независимой от сервера локализации. Свяжите все это вместе с дизайном, который реагирует не только на размер экрана, но и на содержимое. В конце концов, у вас будет сайт, который понравится вашим пользователям всех языков, который может поддерживаться авторами и переводчиками, как если бы это был простой сайт с одной локалью.