Руководство по переводу

Это руководство объясняет, как внести свой вклад в переводы RimSort. Проект использует систему интернационализации (i18n) Qt на базе PySide6 с QTranslator.

Содержание

  1. Руководство по переводу
    1. Содержание
    2. Обзор системы перевода
    3. Структура проекта
    4. Сборка файлов переводов
      1. Компиляция переводов
      2. Извлечение новых исходных строк
    5. Поддерживаемые языки
    6. Инструмент помощи переводу
      1. Доступные команды
        1. Интерактивный режим (рекомендуется новичкам)
        2. Базовые команды
        3. Продвинутые команды
      2. Примеры команд
      3. Возможности
      4. Дополнительные параметры конфигурации
    7. Быстрый старт
      1. Вариант 1: Интерактивный режим (рекомендуется новичкам)
      2. Вариант 2: Режим командной строки
    8. Как внести свой вклад в переводы
      1. Предварительные требования
      2. Шаг 1: Настройка окружения
      3. Шаг 2: Выберите тип вклада
        1. Вариант A: Улучшить существующий перевод
        2. Вариант B: Создать перевод на новый язык
      4. Шаг 3: Процесс перевода
        1. Использование текстового редактора (рекомендуется)
        2. Использование Qt Linguist (необязательно)
      5. Шаг 4: Правила перевода
        1. Понимание контекста
        2. Рекомендации по переводу
        3. Пример перевода
      6. Шаг 5: Проверка перевода
        1. 5.1 Проверка файла перевода
        2. 5.2 Компиляция и проверка перевода
        3. 5.3 Проверка в приложении
      7. Шаг 6: Отправка вклада
    9. Отслеживание статуса перевода
    10. Сопровождение и обновление
      1. Когда меняется исходный код
      2. Формат файла перевода

Обзор системы перевода

RimSort использует систему перевода Qt со следующими компонентами:

  • Файлы .ts: исходные файлы переводов (формат XML), которые редактируют переводчики
    • Файлы .qm: скомпилированные бинарные файлы переводов, используемые приложением. Они генерируются командой just i18n-compile (и CI) и коммитятся в репозиторий.
  • QTranslator: движок перевода Qt, который загружает и применяет переводы

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

RimSort/
├── locales/           # Каталог файлов переводов
│   ├── en_US.ts      # Английский (исходный язык)
│   ├── zh_CN.ts      # Упрощённый китайский
│   ├── zh_TW.ts      # Традиционный китайский
│   ├── fr_FR.ts      # Французский
│   ├── de_DE.ts      # Немецкий
│   ├── es_ES.ts      # Испанский
│   ├── ja_JP.ts      # Японский
│   ├── pt_BR.ts      # Португальский (Бразилия)
│   ├── ru_RU.ts      # Русский
│   ├── tr_TR.ts      # Турецкий
│   └── ko_KR.ts      # Корейский
└── app/
    └── controllers/
        └── language_controller.py  # Управление языками

Сборка файлов переводов

Исходные файлы переводов (.ts) должны быть скомпилированы в бинарные файлы .qm, прежде чем приложение сможет их загрузить. Скомпилированные файлы .qm коммитятся в репозиторий, чтобы пользователи могли пользоваться переводами прямо из клона или релиза без перекомпиляции. В ходе разработки они (пере)генерируются командой just i18n-compile или just dev-setup.

Компиляция переводов

После изменения любого файла .ts скомпилируйте все переводы:

just i18n-compile

Эта команда сначала удаляет существующие файлы .qm (чтобы убрать устаревшие артефакты от переименованных или удалённых файлов .ts), затем запускает pyside6-lrelease для каждого файла .ts в каталоге locales/ и создаёт соответствующий файл .qm. Рецепты dev-setup и build выполняют это автоматически.

Извлечение новых исходных строк

Когда в исходный код Python добавляются новые переводимые строки (через QCoreApplication.translate()), обновите файлы .ts, чтобы включить их:

just i18n-update

Эта команда запускает pyside6-lupdate для сканирования app/ и объединения новых строк во все существующие файлы .ts. Затем переводчики могут заполнить новые записи.

Поддерживаемые языки

Код языкаНазвание языкаСтатус
en_USEnglishПолный (исходный)
zh_CN简体中文 (упрощённый китайский)Полный
zh_TW正體中文 (традиционный китайский)Полный
fr_FRFrançais (французский)Полный
de_DEDeutsch (немецкий)Полный
es_ESEspañol (испанский)Полный
ja_JP日本語 (японский)Полный
pt_BRPortuguês (Brasil)Полный
ru_RUРусскийПолный
tr_TRTürkçe (турецкий)Полный
ko_KR한국어 (корейский)Полный

Инструмент помощи переводу

В проекте есть скрипт translation_helper.py, помогающий в работе с переводами.

Важно: этот инструмент реализован на командах PySide6 и требует корректно настроенного окружения для разработки. Пожалуйста, настройте окружение по руководству по настройке окружения разработчика, прежде чем использовать этот инструмент.

Доступные команды

Инструмент помощи переводу предоставляет следующие команды:

Интерактивный режим (рекомендуется новичкам)

  • --interactive или -i: запуск интерактивного меню для управления переводами
    • Понятные подсказки по выбору языка
    • Выбор сервиса с описаниями
    • Параметры конфигурации (таймаут, повторы, конкурентность)
    • Подтверждение перед операциями
    • Подходит для нетехнических переводчиков

Базовые команды

Все базовые команды поддерживают как режим одного языка, так и пакетный режим (все языки):

  • check [language]: проверка полноты перевода
    • check zh_CN — проверить только конкретный язык
    • check — проверить все языки сразу (пакетная операция)
    • Опция: --json для структурированного вывода JSON (для программного использования)
  • stats: статистика переводов для всех языков
    • Опция: --json для структурированного вывода JSON
  • validate [language]: проверка формата и содержимого файла переводов, автоматическое исправление частых проблем
    • validate zh_CN — проверить только конкретный язык
    • validate — проверить все языки сразу (пакетная операция)
  • update-ts [language]: обновление файлов .ts новыми строками из исходного языка
    • update-ts zh_CN — обновить только конкретный язык
    • update-ts — обновить все языки сразу (пакетная операция)
  • compile [language]: компиляция файлов .ts в бинарный формат .qm
    • compile zh_CN — скомпилировать только конкретный язык
    • compile — скомпилировать все языки сразу (пакетная операция)

Продвинутые команды

  • auto-translate [language] --service [google|deepl|openai]: автоматический перевод незавершённых строк с помощью различных сервисов перевода
    • auto-translate zh_CN --service google — перевести автоматически только конкретный язык
    • auto-translate --service google — перевести автоматически все языки сразу (пакетная операция)
    • Google Translate: бесплатно, поддерживает все языки, API-ключ не требуется
    • DeepL: высокое качество, требует API-ключ, поддерживает основные европейские языки (EN, FR, DE, ES, PT, IT, NL, PL; RU не поддерживается)
    • OpenAI GPT: переводы на основе ИИ, требует API-ключ, поддерживает основные языки (RU, TR, PT-BR не поддерживаются)
    • Опции: --api-key для аутентификации, --model для выбора модели OpenAI, --continue-on-failure для пропуска неудачных переводов
    • Дополнительные опции: --timeout, --max-retries, --max-concurrent, --no-cache
  • process [language] --service [google|deepl|openai]: рабочий процесс в один клик — выполняет последовательность update-ts → auto-translate → compile
    • process zh_CN --service google — полный процесс для конкретного языка
    • process --service google — полный процесс для всех языков (пакетная операция)
    • Использует те же сервисы и ограничения, что и auto-translate (Google: все языки; DeepL: основные европейские языки, кроме RU; OpenAI: основные языки, кроме RU/TR/PT-BR)
    • Те же опции, что и у auto-translate: --api-key, --model, --continue-on-failure
    • Параметры конфигурации: --timeout, --max-retries, --max-concurrent, --no-cache

Примеры команд

# Интерактивный режим (рекомендуется новичкам)
python translation_helper.py --interactive
python translation_helper.py -i

# Проверка полноты для всех языков
python translation_helper.py check

# Проверка конкретного языка
python translation_helper.py check zh_CN

# Проверка с выводом JSON (для программного использования)
python translation_helper.py check --json
python translation_helper.py check zh_CN --json

# Статистика по всем языкам
python translation_helper.py stats

# Статистика в формате JSON
python translation_helper.py stats --json

# Проверка и автоисправление всех языков (пакетная операция)
python translation_helper.py validate

# Проверка только конкретного языка
python translation_helper.py validate zh_CN

# Обновление файлов переводов для всех языков (пакетная операция)
python translation_helper.py update-ts

# Обновление файла перевода для конкретного языка
python translation_helper.py update-ts zh_CN

# Автоперевод конкретного языка через Google (бесплатно, API-ключ не нужен)
python translation_helper.py auto-translate zh_CN --service google

# Автоперевод ВСЕХ языков через Google (пакетная операция)
python translation_helper.py auto-translate --service google

# Автоперевод через DeepL (требуется API-ключ)
python translation_helper.py auto-translate zh_CN --service deepl --api-key YOUR_DEEPL_KEY

# Автоперевод всех языков через DeepL (пакетная операция, требуется API-ключ)
python translation_helper.py auto-translate --service deepl --api-key YOUR_DEEPL_KEY

# Автоперевод через OpenAI (требуется API-ключ)
python translation_helper.py auto-translate zh_CN --service openai --api-key YOUR_OPENAI_KEY --model gpt-4

# Автоперевод всех языков через OpenAI (пакетная операция, требуется API-ключ)
python translation_helper.py auto-translate --service openai --api-key YOUR_OPENAI_KEY --model gpt-4

# Автоперевод с настройками конфигурации
python translation_helper.py auto-translate zh_CN --service google --timeout 30 --max-retries 5 --max-concurrent 10

# Автоперевод без использования кэша (только новые переводы)
python translation_helper.py auto-translate zh_CN --service google --no-cache

# Полный рабочий процесс в один клик для конкретного языка
python translation_helper.py process zh_CN --service google

# Полный рабочий процесс для всех языков (пакетная операция)
python translation_helper.py process --service google

# Компиляция конкретного языка
python translation_helper.py compile zh_CN

# Компиляция всех языков (пакетная операция)
python translation_helper.py compile

Возможности

  • Интерактивный режим: понятный пошаговый процесс для новичков и нетехнических переводчиков
  • Пакетные операции: большинство команд поддерживают работу со всеми языками, если конкретный язык не указан
  • Автоисправление при проверке: команда validate автоматически исправляет несоответствия плейсхолдеров и HTML-тегов
  • Несколько сервисов перевода: поддержка Google Translate (бесплатно), DeepL и OpenAI с настраиваемыми моделями
  • Обработка ошибок: надёжная обработка с повторами и исправлением SSL для Google Translate
  • Вывод JSON: структурированный вывод для программного использования и интеграции с CI/CD
  • Отслеживание прогресса: индикаторы выполнения в реальном времени и подробная статистика
  • Параметры конфигурации: настраиваемые таймаут, повторы и лимиты параллельных запросов
  • Конкурентность: оптимизированная параллельная обработка для быстрых пакетных операций

Подробные способы использования см. в разделе «Шаг 5: Проверка перевода».

Дополнительные параметры конфигурации

Инструмент поддержки переводов поддерживает несколько продвинутых опций для тонкой настройки:

  • --timeout (float, по умолчанию: 10.0): таймаут запроса в секундах. Увеличьте для медленных сетей.
  • --max-retries (int, по умолчанию: 3): максимальное количество повторов неудачных запросов с экспоненциальным откатом.
  • --max-concurrent (int, по умолчанию: 5): максимальное количество параллельных API-запросов. Баланс между скоростью и лимитами API.
  • --no-cache: пропустить кэш переводов для текущего запуска. Полезно для принудительного получения свежих переводов.
  • --continue-on-failure: по умолчанию включено. Отключите с помощью --no-continue-on-failure, чтобы прерваться на первой ошибке.

Управление кэшем:

Инструмент поддержки переводов ведёт постоянный файл кэша (.translation_cache.json) для уменьшения количества API-запросов и расходов. Кэшированные переводы автоматически переиспользуются между запусками. Чтобы очистить кэш и запросить свежие переводы:

# Очистить кэш и отключить его для этого запуска
python translation_helper.py auto-translate zh_CN --service google --no-cache

Быстрый старт

Если вы хотите быстро начать работу с переводами, у вас есть два варианта:

Вариант 1: Интерактивный режим (рекомендуется новичкам)

  1. Сделайте форк и клонируйте проект
  2. Настройте окружение для разработки (по руководству по настройке окружения разработчика)
  3. Запустите интерактивный режим: python translation_helper.py --interactive
  4. Следуйте меню:
    • Выберите «Проверить полноту перевода», чтобы узнать, что нужно перевести
    • Выберите «Автоперевести недостающие строки», чтобы заполнить пробелы с помощью ИИ
    • Выберите «Полный процесс», чтобы обновить, перевести и скомпилировать за один раз
  5. Отправьте код: закоммитьте файлы .ts и .qm (файлы .qm также автоматически генерируются just i18n-compile и CI и коммитятся в репозиторий)

Вариант 2: Режим командной строки

  1. Сделайте форк и клонируйте проект
  2. Настройте окружение для разработки (по руководству по настройке окружения разработчика)
  3. Выберите языковой файл: откройте locales/YOUR_LANGUAGE.ts
  4. Отредактируйте переводы: найдите записи, помеченные как type="unfinished", и переведите их
  5. Автопереведите остальные строки (необязательно): python translation_helper.py auto-translate YOUR_LANGUAGE --service google
  6. Скомпилируйте и протестируйте: python translation_helper.py compile YOUR_LANGUAGE
  7. Отправьте код: закоммитьте файлы .ts и .qm (файлы .qm также автоматически генерируются just i18n-compile и CI и коммитятся в репозиторий) Подробные шаги см. в полном руководстве ниже.

Как внести свой вклад в переводы

Предварительные требования

Перед началом работы с переводами подготовьте следующее:

  1. Окружение для разработки (обязательно)
  2. Редактор переводов
    • Рекомендуется: текстовый редактор с подсветкой синтаксиса XML (VS Code, Sublime Text, Notepad++ и т. д.)
    • Необязательно: Qt Linguist (требует отдельной установки окружения разработки Qt)
  3. Инструменты контроля версий
    • Система контроля версий Git
    • Учётная запись GitHub для вклада в код

Шаг 1: Настройка окружения

  1. Сделайте форк репозитория RimSort на GitHub
  2. Клонируйте свой форк:

    git clone https://github.com/YOUR_USERNAME/RimSort.git
    cd RimSort
    
  3. Создайте новую ветку для вашего перевода:

    git checkout -b translation-LANGUAGE_CODE
    # Пример: git checkout -b translation-pt_BR
    

Шаг 2: Выберите тип вклада

Вариант A: Улучшить существующий перевод

  1. Перейдите в каталог locales/
  2. Откройте существующий файл .ts для вашего языка (например, en_US.ts)
  3. Найдите записи, помеченные как type="unfinished" или с пустыми тегами <translation>

Вариант B: Создать перевод на новый язык

  1. Используйте инструменты PySide6 для генерации нового файла перевода:

    Системы Linux/macOS:

    pyside6-lupdate $(find app -name "*.py") -ts locales/NEW_LANGUAGE_CODE.ts -no-obsolete
    # Пример:
    pyside6-lupdate $(find app -name "*.py") -ts locales/pt_BR.ts -no-obsolete
    

    Системы Windows (рекомендуется использовать инструмент поддержки перевода):

    # Используйте инструмент поддержки перевода (рекомендуется, проще)
    python translation_helper.py update-ts pt_BR
    

    Если вам нужно вручную использовать инструменты PySide6, можно обратиться к формату команд Linux/macOS, но рекомендуется использовать инструмент поддержки перевода, чтобы избежать сложной работы с путями.

  2. Обновите атрибут языка в файле:

    <TS version="2.1" language="pt_BR">
    
  3. Зарегистрируйте новый язык в контроллере языков:

    Откройте файл app/controllers/language_controller.py, найдите словарь language_map в методе populate_languages_combobox и добавьте свой язык:

     language_map = {
         "en_US": "English",
         "es_ES": "Español",
         "fr_FR": "Français",
         "de_DE": "Deutsch",
         "zh_CN": "简体中文",
         "ja_JP": "日本語",
         "ru_RU": "Русский",
         "tr_TR": "Türkçe",
         "pt_BR": "Português (Brasil)",
         "zh_TW": "正體中文",
         "ko_KR": "한국어",  # Добавить запись нового языка
     }
    

    Где "pt_BR" — код языка, а "Português (Brasil)" — название языка, которое будет отображаться в интерфейсе настроек.

Шаг 3: Процесс перевода

Использование текстового редактора (рекомендуется)

  1. Откройте файл .ts в вашем текстовом редакторе
  2. Найдите блоки <message>, которые нужно перевести:

    <message>
        <location filename="../app/views/settings_dialog.py" line="896"/>
        <source>Select Language (Restart required to apply changes)</source>
        <translation type="unfinished"></translation>
    </message>
    
  3. Замените пустой перевод своим текстом и удалите type="unfinished":

    <message>
        <location filename="../app/views/settings_dialog.py" line="896"/>
        <source>Select Language (Restart required to apply changes)</source>
        <translation>Выберите язык (для применения изменений требуется перезапуск)</translation>
    </message>
    

Использование Qt Linguist (необязательно)

Если у вас уже установлен Qt Linguist, вы также можете использовать его:

  1. Откройте Qt Linguist
  2. Файл → Открыть → Выберите ваш файл .ts
  3. Переведите каждую строку:
    • Выберите элемент без перевода из списка
    • Введите перевод в поле «Перевод»
    • Отметьте как «Готово», когда результат вас устраивает
    • При необходимости добавьте комментарий переводчика
  4. Сохраните работу: Файл → Сохранить

Шаг 4: Правила перевода

Понимание контекста

Каждая переводимая строка содержит контекстную информацию:

  • Имя файла: указывает, в каком файле находится строка
  • Номер строки: точное место в исходном коде
  • Имя контекста: обычно имя класса (например, «SettingsDialog», «ModInfo»)

Рекомендации по переводу

  1. Сохраняйте форматирование:
    • Сохраняйте \n для переносов строк
    • Сохраняйте плейсхолдеры %s, %d, {0}, {variable_name}
    • Сохраняйте HTML-теги, если они есть
  2. Особенности интерфейса:
    • Делайте переводы краткими для подписей кнопок
    • Учитывайте расширение текста (некоторым языкам нужно больше места)
    • Сохраняйте стиль, соответствующий приложению
  3. Принципы обработки технических терминов:
    • «Mod»: оставляйте как «Mod» на всех языках (термин стал общепринятым)
    • «Workshop»: можно переводить на локальные варианты или оставлять как есть
    • Специфические программные термины: соблюдайте единообразие, рекомендуется сверяться с существующими переводами
    • Названия элементов интерфейса: такие как «Settings», «Options», следует переводить на соответствующий язык
    • Форматы и расширения файлов: такие как «.ts», «.qm» оставляйте без изменений

Пример перевода

<!-- Исходный английский -->
<source>Sort mods</source>
<translation>Сортировать моды</translation>

<!-- С плейсхолдерами -->
<source>Found {count} mods</source>
<translation>Найдено модов: {count}</translation>

<!-- С переносами строк -->
<source>Click OK to save settings
and restart the application</source>
<translation>Нажмите ОК, чтобы сохранить настройки
и перезапустить приложение</translation>

Шаг 5: Проверка перевода

5.1 Проверка файла перевода

Используйте инструмент поддержки перевода для проверки файла:

# Проверка полноты перевода для конкретного языка
python translation_helper.py check YOUR_LANGUAGE
# Пример: python translation_helper.py check zh_CN

# Проверка формата и содержимого файла перевода
python translation_helper.py validate YOUR_LANGUAGE
# Проверяются несоответствия плейсхолдеров, проблемы с HTML-тегами и т. д.

# Просмотр статуса завершения для всех языков
python translation_helper.py stats

5.2 Компиляция и проверка перевода

  1. Скомпилируйте файл перевода:

    # Использование инструмента поддержки перевода (рекомендуется)
    python translation_helper.py compile YOUR_LANGUAGE
    # Пример: python translation_helper.py compile zh_CN
    
    # Или напрямую инструментами PySide6
    pyside6-lrelease locales/YOUR_LANGUAGE.ts
    

    Примечание:

    1. При компиляции в каталоге locales/ создаются соответствующие файлы .qm.
    2. Эти файлы также генерируются с помощью GitHub actions и в таких случаях могут быть артефактами сборки; тогда .qm не коммитятся в контроль версий.
    3. Они автоматически генерируются командами just dev-setup и just build.
    4. В этом проекте файлы .qm также коммитятся в контроль версий, чтобы пользователи могли сразу использовать переводы после скачивания без дополнительных шагов компиляции. (всё ещё нужно, чтобы файлы сгенерировал и закоммитил пользователь; в будущем это будет автоматизировано)

5.3 Проверка в приложении

  1. Запустите RimSort и переключите язык:
    • Выполните python -m app, чтобы запустить приложение
    • Нажмите «Settings» в строке меню
    • Найдите опцию «Language»
    • Выберите свой язык в выпадающем меню
    • При необходимости перезапустите приложение, чтобы применить изменения
  2. Функциональное тестирование:
    • Основной интерфейс: проверьте текст строки меню, панели инструментов и строки состояния
    • Диалог настроек: убедитесь, что все опции и кнопки переведены
    • Управление модами: проверьте подписи функций списка модов, сортировки и фильтрации
    • Сообщения об ошибках: вызовите несколько предупреждений или ошибок и проверьте, переведены ли сообщения
  3. Визуальная проверка:
    • Адаптация текста: убедитесь, что переведённый текст полностью отображается в элементах интерфейса
    • Размеры кнопок: проверьте, помещается ли более длинный переведённый текст в кнопках
    • Макет диалогов: убедитесь, что диалоги сохраняют корректные размеры после отображения переводов
    • Подсказки: наведите курсор на различные элементы и проверьте переводы подсказок

Шаг 6: Отправка вклада

  1. Закоммитьте изменения:

     # Добавьте файлы перевода (.ts исходные файлы и скомпилированные .qm, коммитятся оба)
     git add locales/YOUR_LANGUAGE.ts
     git add locales/YOUR_LANGUAGE.qm
     # Если вы добавили новый язык, обновите также контроллер языков
     git add app/controllers/language_controller.py
     git commit -m "Добавить/Обновить перевод [название языка]"
    

    Примечание:

    1. При компиляции в каталоге locales/ создаются соответствующие файлы .qm.
    2. Файлы .qm коммитятся в репозиторий вместе с файлами .ts, чтобы пользователи могли пользоваться переводами прямо из клона или релиза без перекомпиляции. Они (пере)генерируются just i18n-compile, just dev-setup и CI.
    3. Всегда запускайте just i18n-update, а затем just i18n-compile, и коммитьте изменения и .ts, и .qm файлов.
  2. Запушьте в свой форк:

    git push origin translation-LANGUAGE_CODE
    
  3. Создайте Pull Request:

    • Перейдите на свой форк на GitHub
    • Нажмите «Compare & pull request» или «New Pull Request»
    • Выберите ветку с вашим переводом как ветку-источник
    • Напишите понятный заголовок в формате: «Add Portuguese translation» или «Update Chinese translation»
    • В описании укажите:
      • Процент завершения перевода (например, «Completed 80% of string translations»)
      • Основные изменения
      • Нужно ли дальнейшее тестирование

Примеры заголовков Pull Request:

  • Add French translation (fr_FR)
  • Improve German translation - Fix interface terminology
  • Update Japanese translation - Add settings page translations

Отслеживание статуса перевода

Полноту перевода можно проверить по следующим признакам:

  • Записи type="unfinished" (требуют перевода)
  • Пустые теги <translation></translation>
  • Записи type="obsolete" (может потребоваться проверка)

Сопровождение и обновление

Когда меняется исходный код

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

# Обновите файлы переводов, чтобы включить последние переводимые строки
python translation_helper.py update-ts YOUR_LANGUAGE

Также можно обновить все языки сразу с помощью рецепта just:

just i18n-update

После обновления вам нужно перевести только новые или изменённые строки; существующие переводы будут сохранены.

Формат файла перевода

Файлы .ts используют формат XML со следующей структурой:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="LANGUAGE_CODE">
<context>
    <name>ClassName</name>
    <message>
        <location filename="../path/to/file.py" line="123"/>
        <source>English text</source>
        <translation>Переведённый текст</translation>
    </message>
</context>
</TS>

Спасибо, что помогаете делать RimSort доступным для пользователей по всему миру! 🌍