Общие строки

Как интернационализировать строки, используемые в нескольких компонентах и файлах

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

Проблема с общим контентом

Рассмотрим следующую конфигурацию навигации, используемую во всём вашем приложении:

// navData.ts
export const navData = [
  {
    label: 'Главная',
    description: 'Главная страница',
    href: '/'
  },
  {
    label: 'О компании', 
    description: 'Сведения о компании',
    href: '/about'
  }
];

Чтобы интернационализировать это, обычно приходится:

  1. Превратить компонент в функцию, принимающую функцию перевода
  2. Обновить все места использования, чтобы вызывать её с t
  3. Управлять растущей сложностью по всему коду

Это создаёт издержки на сопровождение и ухудшает читаемость кода. Функция msg решает проблему: она позволяет помечать строки для перевода прямо на месте, а затем декодировать их по мере необходимости.

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

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

// navData.ts — Помечаем строки для перевода
import { msg } from 'gt-react';

export const navData = [
  {
    label: msg('Главная'),
    description: msg('Главная страница'), 
    href: '/'
  },
  {
    label: msg('О компании'),
    description: msg('Сведения о компании'),
    href: '/about'
  }
];
// Component usage - Decode marked strings
import { useMessages } from 'gt-react';
import { navData } from './navData';

function Navigation() {
  const m = useMessages();
  
  return (
    <nav>
      {navData.map((item) => (
        <a key={item.href} href={item.href} title={m(item.description)}>
          {m(item.label)}
        </a>
      ))}
    </nav>
  );
}

Как работает механизм Shared Strings

Система Shared Strings работает в два этапа:

  1. Этап разметки: msg помечает строки метаданными для перевода
  2. Этап декодирования: useMessages декодирует и переводит строки
// msg() кодирует строку вместе с метаданными
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="

// useMessages() декодирует и переводит
const m = useMessages();
const translated = m(encoded); // «Hello, world!» на языке пользователя

Закодированные строки из msg нельзя использовать напрямую — их нужно декодировать с помощью useMessages.

Компоненты

Используйте хук useMessages:

import { useMessages } from 'gt-react';

const encodedString = msg('Привет, мир!');

function MyComponent() {
  const m = useMessages();
  return <div>{m(encodedString)}</div>;
}

Получение исходных строк с decodeMsg

Иногда требуется обратиться к исходной строке без перевода — например, для логирования, отладки или сравнения. Используйте decodeMsg, чтобы получить исходный текст:

import { decodeMsg } from 'gt-react';

const encoded = msg('Привет, мир!');
const original = decodeMsg(encoded); // «Привет, мир!» (исходный текст)
const translated = m(encoded); // «Привет, мир!» (на языке пользователя)

// Удобно для логирования и отладки
console.log('Исходная строка:', decodeMsg(encoded));
console.log('Переведённая строка:', m(encoded));

Сценарии использования decodeMsg

  • Разработка и отладка: Записывайте исходные строки в логи для диагностики
  • Обработка фолбэков: Используйте исходный текст, если перевод недоступен
  • Сравнение строк: Сравнивайте с известными исходными значениями
  • Аналитика: Отслеживайте использование исходных строк
// Пример: обработка запасного варианта
function getDisplayText(encodedStr) {
  const m = useMessages();
  try {
    return m(encodedStr);
  } catch (error) {
    console.warn('Не удалось выполнить перевод, используется исходный текст:', decodeMsg(encodedStr));
    return decodeMsg(encodedStr);
  }
}

Использование переменных

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

// Отметьте строку с переменными
const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: msg('Базовый план включает {items} позиций', { items })
  }
];
// Использовать в компоненте
function PricingCard() {
  const m = useMessages();
  
  return (
    <div>
      <h3>{pricing[0].name}</h3>
      <p>{m(pricing[0].description)}</p>
    </div>
  );
}

Формат сообщений ICU

Для расширенного форматирования используйте синтаксис ICU:

const count = 10;
const message = msg('В корзине {count, plural, =0 {нет товаров} =1 {1 товар} one {# товар} few {# товара} many {# товаров} other {# товара}}', { count });

Подробнее о формате сообщений ICU — в документации Unicode.

Примеры

Настройка навигации

// config/navigation.ts
import { msg } from 'gt-react';

export const mainNav = [
  {
    label: msg('Главная'),
    href: '/',
    icon: 'home'
  },
  {
    label: msg('Продукты'),
    href: '/products', 
    icon: 'package'
  },
  {
    label: msg('О нас'),
    href: '/about',
    icon: 'info'
  }
];

export const footerLinks = [
  {
    title: msg('Компания'),
    links: [
      { label: msg('О компании'), href: '/about' },
      { label: msg('Вакансии'), href: '/careers' },
      { label: msg('Контакты'), href: '/contact' }
    ]
  },
  {
    title: msg('Поддержка'), 
    links: [
      { label: msg('Справка'), href: '/help' },
      { label: msg('Документация'), href: '/docs' },
      { label: msg('Справочник по API'), href: '/api' }
    ]
  }
];
// components/Navigation.tsx
import { useMessages } from 'gt-react';
import { mainNav } from '../config/navigation';

function Navigation() {
  const m = useMessages();
  
  return (
    <nav>
      {mainNav.map((item) => (
        <a key={item.href} href={item.href}>
          <Icon name={item.icon} />
          {m(item.label)}
        </a>
      ))}
    </nav>
  );
}

Настройка формы

// config/forms.ts
import { msg } from 'gt-react';

export const formMessages = {
  placeholders: {
    email: msg('Введите адрес электронной почты'),
    password: msg('Введите пароль'),
    message: msg('Введите ваше сообщение…')
  },
  actions: {
    send: msg('Отправить сообщение'),
    save: msg('Сохранить изменения'),
    cancel: msg('Отменить')
  },
  validation: {
    required: msg('Это поле обязательно для заполнения'),
    email: msg('Введите корректный адрес электронной почты'),
    minLength: msg('Не менее {min} символов', { min: 8 }),
    maxLength: msg('Не более {max} символов', { max: 100 })
  },
  success: {
    saved: msg('Изменения успешно сохранены'),
    sent: msg('Сообщение успешно отправлено'),
    updated: msg('Профиль обновлён')
  },
  errors: {
    network: msg('Сетевая ошибка — попробуйте ещё раз'),
    server: msg('Ошибка сервера — обратитесь в службу поддержки'),
    timeout: msg('Превышено время ожидания — попробуйте ещё раз')
  }
};
// components/ContactForm.tsx
import { useMessages } from 'gt-react';
import { formMessages } from '../config/forms';

function ContactForm() {
  const m = useMessages();
  const [errors, setErrors] = useState({});
  
  return (
    <form>
      <input 
        type="email"
        placeholder={m(formMessages.placeholders.email)}
        required
      />
      {errors.email && <span>{m(formMessages.validation.email)}</span>}
      
      <button type="submit">
        {m(formMessages.actions.send)}
      </button>
    </form>
  );
}

Динамическая генерация контента

// utils/productData.ts
import { msg } from 'gt-react';

function mockProducts() {
  return [
    { name: 'iPhone 15', company: 'Apple', category: 'Electronics' },
    { name: 'Galaxy S24', company: 'Samsung', category: 'Electronics' }
  ];
}

export function getProductData() {
  const products = mockProducts();
  
  return products.map(product => ({
    ...product,
    description: msg('{name} — это продукт категории «{category}» от {company}', {
      name: product.name,
      category: product.category,
      company: product.company
    })
  }));
}
// components/ProductList.tsx
import { useMessages } from 'gt-react';
import { getProductData } from '../utils/productData';

function ProductList() {
  const m = useMessages();
  const products = getProductData();
  
  return (
    <div>
      {products.map(product => (
        <div key={product.name}>
          <h3>{product.name}</h3>
          <p>{m(product.description)}</p>
        </div>
      ))}
    </div>
  );
}

Частые проблемы

Непосредственное использование кодированных строк

Никогда не используйте результат msg напрямую:

// ❌ Неверно — используется закодированная строка напрямую
const encoded = msg('Hello, world!');
return <div>{encoded}</div>; // Отображает закодированную строку, а не перевод

// ✅ Правильно — сначала декодируйте строку  
const encoded = msg('Hello, world!');
const m = useMessages();
return <div>{m(encoded)}</div>; // Отображает корректный перевод

Динамический контент в msg()

Строки должны быть известны на этапе сборки:

// ❌ Неверно — динамический шаблонный литерал
const name = 'John';
const message = msg(`Hello, ${name}`); // Ошибка на этапе сборки

// ✅ Правильно — используйте переменные  
const name = 'John';
const message = msg('Hello, {name}', { name });

Часто забывают декодировать

Каждую строку msg нужно декодировать:

// ❌ Отсутствует декодирование
const config = {
  title: msg('Панель управления'),
  subtitle: msg('С возвращением')
};

// Позже в компоненте — забыли декодировать
return <h1>{config.title}</h1>; // Отображается закодированная строка

// ✅ Правильно — декодируйте при использовании
const m = useMessages();
return <h1>{m(config.title)}</h1>; // Отображается переведённый заголовок

Следующие шаги

Насколько полезно это руководство?

Общие строки