>
← Повернутися на головну

Інтернаціоналізація (i18n)

Створення багатомовних веб-додатків: i18next, локалізація дат/чисел, RTL підтримка

📅 Оновлено: 7 лютого 2026 ⏱️ Час читання: 35 хв 🎯 Рівень: Середній
📘 Термінологія:
  • i18n (Internationalization) — проєктування додатку для підтримки різних мов (18 літер між i та n)
  • l10n (Localization) — адаптація контенту під конкретну мову/регіон (10 літер між l та n)
  • Locale — код мови та регіону (наприклад: uk-UA, en-US, fr-FR)
  • RTL (Right-to-Left) — мови з написанням справа наліво (арабська, іврит)

Частина 1: Основи i18n

Чому важлива інтернаціоналізація?

Структура локалізаційних файлів

project/ ├── public/ │ └── locales/ │ ├── uk/ │ │ ├── common.json │ │ ├── calculator.json │ │ └── errors.json │ ├── en/ │ │ ├── common.json │ │ ├── calculator.json │ │ └── errors.json │ ├── ru/ │ │ └── ... │ └── de/ │ └── ...

Частина 2: i18next — найпопулярніший framework

1 Встановлення i18next
# Для Vanilla JS npm install i18next i18next-http-backend i18next-browser-languagedetector # Для React npm install react-i18next i18next # Для Vue npm install vue-i18next i18next
2 Налаштування i18next (Vanilla JS)

Створіть src/i18n.js:

import i18next from 'i18next'; import HttpBackend from 'i18next-http-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; i18next .use(HttpBackend) // Завантаження файлів з /public/locales .use(LanguageDetector) // Автовизначення мови браузера .init({ fallbackLng: 'en', // Резервна мова debug: true, // Підтримувані мови supportedLngs: ['uk', 'en', 'ru', 'de', 'fr'], // Налаштування бекенду backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', }, // Налаштування детектора detection: { order: ['querystring', 'cookie', 'localStorage', 'navigator'], caches: ['localStorage', 'cookie'], }, // Інтерполяція interpolation: { escapeValue: false, // React вже escaped }, }); export default i18next;

Locale файл public/locales/uk/calculator.json:

{ "title": "Наукові калькулятори", "subtitle": "280+ безкоштовних калькуляторів онлайн", "quadratic": { "title": "Квадратне рівняння", "description": "Розв'язання квадратного рівняння виду ax² + bx + c = 0", "coefficient_a": "Коефіцієнт a", "coefficient_b": "Коефіцієнт b", "coefficient_c": "Коефіцієнт c", "calculate": "Обчислити", "results": { "roots": "Корені рівняння:", "discriminant": "Дискримінант:", "no_roots": "Рівняння не має дійсних коренів" } }, "errors": { "invalid_input": "Некоректне введення", "coefficient_zero": "Коефіцієнт a не може дорівнювати нулю", "calculation_error": "Помилка обчислення: {{error}}" }, "common": { "loading": "Завантаження...", "error": "Помилка", "success": "Успіх", "cancel": "Скасувати", "save": "Зберегти" } }

Англійський переклад public/locales/en/calculator.json:

{ "title": "Scientific Calculators", "subtitle": "280+ free online calculators", "quadratic": { "title": "Quadratic Equation", "description": "Solve quadratic equation ax² + bx + c = 0", "coefficient_a": "Coefficient a", "coefficient_b": "Coefficient b", "coefficient_c": "Coefficient c", "calculate": "Calculate", "results": { "roots": "Equation roots:", "discriminant": "Discriminant:", "no_roots": "The equation has no real roots" } }, "errors": { "invalid_input": "Invalid input", "coefficient_zero": "Coefficient a cannot be zero", "calculation_error": "Calculation error: {{error}}" }, "common": { "loading": "Loading...", "error": "Error", "success": "Success", "cancel": "Cancel", "save": "Save" } }
3 Використання в коді
import './i18n'; import i18next from 'i18next'; // Простий переклад document.querySelector('h1').textContent = i18next.t('title'); // Вкладені ключі document.querySelector('.subtitle').textContent = i18next.t('quadratic.description'); // З інтерполяцією const error = 'Division by zero'; alert(i18next.t('errors.calculation_error', { error })); // Зміна мови function changeLanguage(lng) { i18next.changeLanguage(lng, () => { // Оновити всі переклади на сторінці updatePageTranslations(); }); } // Language switcher document.getElementById('lang-uk').addEventListener('click', () => changeLanguage('uk')); document.getElementById('lang-en').addEventListener('click', () => changeLanguage('en'));

HTML з data-атрибутами:

<div class="calculator"> <h1 data-i18n="quadratic.title"></h1> <p data-i18n="quadratic.description"></p> <form> <label data-i18n="quadratic.coefficient_a"></label> <input type="number" name="a"> <button data-i18n="quadratic.calculate"></button> </form> <!-- Language switcher --> <select id="language-select"> <option value="uk">Українська</option> <option value="en">English
// Функція оновлення перекладів на сторінці function updatePageTranslations() { document.querySelectorAll('[data-i18n]').forEach(element => { const key = element.getAttribute('data-i18n'); element.textContent = i18next.t(key); }); document.querySelectorAll('[data-i18n-placeholder]').forEach(element => { const key = element.getAttribute('data-i18n-placeholder'); element.placeholder = i18next.t(key); }); } // Оновити при завантаженні i18next.on('initialized', updatePageTranslations); i18next.on('languageChanged', updatePageTranslations);

Частина 3: React i18next

1 Налаштування React i18next

src/i18n.js:

import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import HttpBackend from 'i18next-http-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; i18n .use(HttpBackend) .use(LanguageDetector) .use(initReactI18next) .init({ fallbackLng: 'en', supportedLngs: ['uk', 'en', 'ru'], backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', }, react: { useSuspense: true, }, }); export default i18n;

src/App.jsx:

import React, { Suspense } from 'react'; import { useTranslation } from 'react-i18next'; import './i18n'; function Calculator() { const { t, i18n } = useTranslation('calculator'); const changeLanguage = (lng) => { i18n.changeLanguage(lng); }; return ( <div> <h1>{t('title')}</h1> <p>{t('quadratic.description')}</p> <select value={i18n.language} onChange={(e) => changeLanguage(e.target.value)}> <option value="uk">Українська</option> <option value="en">English</option> <option value="ru">Русский</option> </select> <button>{t('quadratic.calculate')}</button> </div> ); } function App() { return ( <Suspense fallback="Loading..."> <Calculator /> </Suspense> ); } export default App;

Частина 4: Локалізація дат, чисел та валют

Intl API (вбудований у браузер)

// Дати const date = new Date(); const ukDate = new Intl.DateTimeFormat('uk-UA').format(date); // "07.02.2026" const enDate = new Intl.DateTimeFormat('en-US').format(date); // "2/7/2026" const fullDate = new Intl.DateTimeFormat('uk-UA', { dateStyle: 'full', timeStyle: 'short' }).format(date); // "п'ятниця, 7 лютого 2026 р., 14:30" // Числа const number = 1234567.89; const ukNumber = new Intl.NumberFormat('uk-UA').format(number); // "1 234 567,89" const enNumber = new Intl.NumberFormat('en-US').format(number); // "1,234,567.89" // Валюти const price = 1234.56; const uah = new Intl.NumberFormat('uk-UA', { style: 'currency', currency: 'UAH' }).format(price); // "1 234,56 ₴" const usd = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price); // "$1,234.56" // Відносний час const rtf = new Intl.RelativeTimeFormat('uk', { numeric: 'auto' }); rtf.format(-1, 'day'); // "вчора" rtf.format(2, 'day'); // "через 2 дні" rtf.format(-3, 'month'); // "3 місяці тому"

Частина 5: RTL (Right-to-Left) підтримка

1 Визначення RTL мов
const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur']; function isRTL(language) { return RTL_LANGUAGES.includes(language); } // Встановлення direction function setDirection(language) { const direction = isRTL(language) ? 'rtl' : 'ltr'; document.documentElement.setAttribute('dir', direction); document.documentElement.setAttribute('lang', language); } i18next.on('languageChanged', (lng) => { setDirection(lng); });

CSS для RTL:

/* Замість margin-left/right використовуйте logical properties */ .container { margin-inline-start: 20px; /* margin-left для LTR, margin-right для RTL */ margin-inline-end: 10px; /* margin-right для LTR, margin-left для RTL */ padding-inline: 15px; /* padding-left та padding-right */ } /* Або з fallback */ .card { float: left; } [dir="rtl"] .card { float: right; } /* Flexbox автоматично змінює напрямок */ .flex-container { display: flex; flex-direction: row; /* Автоматично реверсується в RTL */ } /* Icons що потребують дзеркального відображення */ [dir="rtl"] .icon-arrow-right { transform: scaleX(-1); }
مثال RTL (Arabic):

هذا مثال على النص من اليمين إلى اليسار في اللغة العربية

Частина 6: Best Practices

✅ Рекомендації:
  1. Структуруйте переклади за функціональністю, а не за сторінками
  2. Використовуйте намespaces для великих проектів (common, calculator, errors)
  3. Pluralization — підтримка множини:
    { "item": "{{count}} елемент", "item_plural": "{{count}} елементи", "item_many": "{{count}} елементів" }
  4. Context — різні переклади для різних контекстів:
    { "save": "Зберегти", "save_male": "Збережений", "save_female": "Збережена" }
  5. Не hardcode тексти — всі тексти через i18n
  6. Тестуйте з різними мовами — особливо короткими/довгими словами
  7. Locale detection order: URL → Cookie → LocalStorage → Browser
⚠️ Поширені помилки:
  • ❌ Хардкод тексту у JS/HTML
  • ❌ Конкатенація строк (замість інтерполяції)
  • ❌ Абсолютні позиції (left/right) замість logical properties
  • ❌ Ігнорування RTL testing
  • ❌ Використання SVG з текстом (краще font icons)

Частина 5: Translation Management Platforms

Crowdin — Translation Management System

1 Налаштування Crowdin проекту
  1. Signup на Crowdin.com
  2. Створіть новий проект → Software Localization
  3. Виберіть source language: Ukrainian
  4. Додайте target languages: English, Russian, German, French
  5. Upload source files:
    # Crowdin CLI npm install -g @crowdin/cli # crowdin.yml project_id: '12345' api_token: 'YOUR_API_TOKEN' files: - source: /public/locales/uk/*.json translation: /public/locales/%two_letters_code%/%original_file_name%
  6. Upload files:
    crowdin upload sources
  7. Download перекладені файли:
    crowdin download

Lokalise Alternative

Platform Безкоштовний план GitHub інтеграція Machine Translation Ціна
Crowdin ✅ Для open-source ✅ Так ✅ Google Translate, DeepL $0-99/міс
Lokalise ⚠️ 14 днів trial ✅ Так ✅ Так $120-600/міс
Weblate ✅ Self-hosted завжди free ✅ Так ✅ Так €0 (self-host)
Transifex ✅ Для open-source ✅ Так ✅ Так $199-999/міс

Частина 6: SEO для багатомовних сайтів

URL Structure для мов

Підхід Приклад Переваги Недоліки
Subdirectories site.com/uk/
site.com/en/
✅ Простота
✅ Один домен
⚠️ Довші URLs
Subdomains uk.site.com
en.site.com
✅ Незалежне хостування ❌ Розділений domain authority
ccTLDs site.com.ua
site.com
✅ Найкраще geo-targeting ❌ Дорого
❌ Складне управління
Query params site.com?lang=uk ❌ НЕ рекомендовано для SEO
✅ Рекомендація: Використовуйте subdirectories (/uk/, /en/) — найкращий баланс між SEO та простотою

hreflang теги для багатомовності

Sitemap для мультимовності

https://site.com/uk/calculator https://site.com/en/calculator

Частина 7: Testing багатомовності

Automated і18n Testing

// tests/i18n.test.js import i18n from '../src/i18n'; describe('i18n Tests', () => { test('All translations loaded', () => { const languages = ['uk', 'en', 'ru']; languages.forEach(lang => { expect(i18n.hasResourceBundle(lang, 'common')).toBe(true); }); }); test('No missing translations', () => { const ukKeys = Object.keys(i18n.getResourceBundle('uk', 'common')); const enKeys = Object.keys(i18n.getResourceBundle('en', 'common')); expect(ukKeys.sort()).toEqual(enKeys.sort()); }); test('Pluralization works', () => { i18n.changeLanguage('uk'); expect(i18n.t('item', { count: 1 })).toBe('1 елемент'); expect(i18n.t('item', { count: 2 })).toBe('2 елементи'); expect(i18n.t('item', { count: 5 })).toBe('5 елементів'); }); test('Interpolation works', () => { i18n.changeLanguage('uk'); const result = i18n.t('welcome', { name: 'Олександр' }); expect(result).toBe('Вітаємо, Олександр!'); }); test('Fallback to default language', () => { i18n.changeLanguage('unknown-lang'); // Повинен fallback до 'uk' (default) expect(i18n.t('common:save')).not.toContain('common:save'); }); });

Visual Regression Testing для RTL

// Playwright visual test import { test, expect } from '@playwright/test'; test('RTL Layout Screenshot Comparison', async ({ page }) => { // LTR (українська) await page.goto('http://localhost:3000/uk/calculator'); await expect(page).toHaveScreenshot('calculator-uk-ltr.png'); // RTL (арабська) await page.goto('http://localhost:3000/ar/calculator'); await expect(page).toHaveScreenshot('calculator-ar-rtl.png'); // Перевірка що елементи дзеркальні const buttonLTR = await page.locator('.submit-button').boundingBox(); await page.goto('http://localhost:3000/ar/calculator'); const buttonRTL = await page.locator('.submit-button').boundingBox(); // Button повинен бути з протилежного боку expect(buttonLTR.x).toBeGreaterThan(buttonRTL.x); });

Частина 8: Advanced Features

Lazy Loading translations

// i18n.js зі lazy loading import i18n from 'i18next'; import Backend from 'i18next-http-backend'; i18n .use(Backend) .init({ lng: 'uk', fallbackLng: 'uk', backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', // Lazy load namespaces allowMultiLoading: false }, ns: ['common'], // Завантажити тільки common на старті defaultNS: 'common' }); // Динамічне завантаження namespace async function loadCalculatorNamespace() { await i18n.loadNamespaces('calculator'); // Тепер можна використовувати t('calculator:solve') }

Translation Cache

// Caching translations у localStorage import i18n from 'i18next'; import Backend from 'i18next-http-backend'; import LocalStorageBackend from 'i18next-localstorage-backend'; import ChainedBackend from 'i18next-chained-backend'; i18n .use(ChainedBackend) .init({ backend: { backends: [ LocalStorageBackend, // Спершу localStorage Backend // Потім HTTP ], backendOptions: [{ expirationTime: 7 * 24 * 60 * 60 * 1000 // 7 днів }, { loadPath: '/locales/{{lng}}/{{ns}}.json' }] } });

Context-aware translations

// uk/common.json { "delete": "Видалити", "delete_male": "Видалений", "delete_female": "Видалена", "delete_plural": "Видалені" }
// Використання context const gender = user.gender; // 'male', 'female', або null const count = items.length; const key = count > 1 ? 'delete_plural' : gender === 'male' ? 'delete_male' : gender === 'female' ? 'delete_female' : 'delete'; const message = t(key);

Частина 9: Best Practices

✅ Translation Quality Guidelines:
  1. Complete sentences — ніколи не розбивайте речення на частини:
    // ❌ ПОГАНО { "greeting_start": "Вітаємо,", "greeting_end": "в нашому додатку!" } // ✅ ДОБРЕ { "greeting": "Вітаємо, {{name}}, в нашому додатку!" }
  2. Context comments — додавайте коментарі для перекладачів:
    { "save_comment": "Button label for saving calculator results", "save": "Зберегти" }
  3. Character limit warnings — вка зуйте якщо є обмеження:
    { "button_short_comment": "Max 10 characters for mobile button", "button_short": "OK" }
  4. Variables naming — зрозумілі назви змінних:
    // ❌ ПОГАНО { "msg": "{{a}} має {{b}} {{c}}" } // ✅ ДОБРЕ { "user_has_items": "{{userName}} має {{count}} елементів" }
  5. Separation of concerns — окремі файли для різних функцій:
    locales/
      uk/
        common.json       — загальні тексти (кнопки, лейбли)
        calculator.json   — математична термінологія
        errors.json       — повідомлення про помилки
        validation.json   — валідація форм

Частина 10: Troubleshooting

Проблема 1: Missing translations показує ключ

Симптом: Замість перекладу відображається calculator:solve

Рішення:

// Перевірте чи завантажений namespace console.log(i18n.hasResourceBundle('uk', 'calculator')); // => false // Завантажіть namespace await i18n.loadNamespaces('calculator'); // Або налаштуйте default namespaces i18n.init({ ns: ['common', 'calculator'], // Завантажити обидва defaultNS: 'common' });

Проблема 2: Pluralization не працює

Симптом: Завжди показує одну форму множини

Рішення:

// Правильна структура для української (3 форми) { "item_one": "{{count}} елемент", // 1, 21, 31... "item_few": "{{count}} елементи", // 2-4, 22-24... "item_many": "{{count}} елементів" // 0, 5-20, 25-30... }

Проблема 3: RTL layout ламається

Симптом: Арабська версія виглядає однаково як LTR

Рішення:

// Динамічно встановлюйте dir attribute useEffect(() => { const isRTL = ['ar', 'he', 'fa', 'ur'].includes(i18n.language); document.documentElement.dir = isRTL ? 'rtl' : 'ltr'; document.documentElement.lang = i18n.language; }, [i18n.language]);

Висновок

Інтернаціоналізація — не luxury, а necessity для глобальних проектів:

  • i18next — найкращий вибір для більшості проектів
  • Intl API — вбудована локалізація дат/чисел
  • RTL підтримка — logical properties + dir attribute
  • Pluralization + Context — для якісних перекладів
🚀 Рекомендований workflow:
  1. Встановіть i18next з першого дня проекту
  2. Створіть структуру /public/locales/
  3. Використовуйте CSS logical properties
  4. Налаштуйте auto language detection
  5. Додайте language switcher у UI
  6. Тестуйте з RTL мовами

Як користуватися шпаргалкою

Ця шпаргалка зосереджує найважливіші формули, правила та визначення теми в компактному форматі для швидкого пошуку та підготовки до іспитів. Матеріал систематизований від базових понять до просунутих результатів.

Ефективне використання

Використовуйте шпаргалку поряд з розв'язуванням задач — не для списування, а як довідник формул. Спершу спробуйте пригадати формулу самостійно, потім звіртеся з довідником. Регулярне повторення формує стійку пам'ять.

Часті запитання (FAQ)

Які ключові формули та правила містить шпаргалка з інтернаціоналізація (i18n)?
Ця шпаргалка з 'Інтернаціоналізація (i18n)' включає: основні означення, головні формули у компактному вигляді, правила обчислень, типові підстановки та приклади застосування. Все систематизовано для швидкого пошуку.
Для кого призначена ця шпаргалка з інтернаціоналізація (i18n)?
Шпаргалка з 'Інтернаціоналізація (i18n)' орієнтована на студентів університетів та учнів старшої школи, а також на всіх, хто хоче швидко освіжити знання перед іспитом або при вирішенні практичних задач.
Як використовувати шпаргалку з інтернаціоналізація (i18n) при підготовці до іспиту?
Оптимальна стратегія: спершу вивчіть теорію, потім використовуйте шпаргалку як довідник при розв'язанні задач. За 1–2 дні до іспиту перегляньте шпаргалку цілком, звертаючи увагу на формули, які ви плутаєте.
Чи охоплює ця шпаргалка всю програму курсу з інтернаціоналізація (i18n)?
Шпаргалка з 'Інтернаціоналізація (i18n)' охоплює стандартну університетську програму: всі ключові теореми, формули та методи. Матеріал структурований від базових понять до просунутих результатів.
Де ще можна попрактикуватися з інтернаціоналізація (i18n) після вивчення шпаргалки?
Після роботи зі шпаргалкою рекомендуємо: тренажери вправ на calculator.party (миттєвий зворотний зв'язок), розв'язані задачі (показують метод покроково) та онлайн-калькулятори для перевірки власних результатів.