Основы переводов

Полное руководство по системе переводов и локализации в MNRFY Framework

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

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

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

Организация файлов

src/translations/
├── ru.json              # Русский язык
├── en.json              # Английский язык  
├── uk.json              # Украинский язык
├── zh.json              # Китайский язык
├── de.json              # Немецкий язык
├── fr.json              # Французский язык
└── es.json              # Испанский язык
Стандарт: Имена файлов должны соответствовать стандарту ISO 639-1 (двухбуквенные коды языков).

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

{
    "welcome": "Добро пожаловать",
    "greeting": "Привет, :name!",
    "menu": {
        "home": "Главная",
        "about": "О нас",
        "contact": "Контакты"
    },
    "messages": {
        "count": [
            "У вас :count сообщение",
            "У вас :count сообщения", 
            "У вас :count сообщений"
        ]
    },
    "order": {
        "total": "Итого: :amount :currency",
        "status": {
            "pending": "Ожидает",
            "completed": "Завершен",
            "cancelled": "Отменен"
        }
    }
}

Базовое использование переводов

Простые переводы

<!-- Базовые переводы -->
{{ t('welcome') }}                           <!-- Добро пожаловать -->
{{ t('greeting') }}                          <!-- Привет, :name! (без параметров) -->

<!-- Вложенные ключи -->
{{ t('menu.home') }}                         <!-- Главная -->
{{ t('menu.about') }}                        <!-- О нас -->
{{ t('order.status.pending') }}              <!-- Ожидает -->

<!-- Использование в HTML -->
<nav>
    <a href="/">{{ t('menu.home') }}</a>
    <a href="/about">{{ t('menu.about') }}</a>
    <a href="/contact">{{ t('menu.contact') }}</a>
</nav>

Переводы с параметрами

<!-- Передача параметров -->
{{ t('greeting', {'name': user.name}) }}       <!-- Привет, Иван! -->
{{ t('order.total', {
    'amount': order.total,
    'currency': 'RUB'
}) }}                                        <!-- Итого: 1500 RUB -->

<!-- Множественные параметры -->
{% set params = {
    'user': user.name,
    'product': product.title,
    'date': order.created_at | date('d.m.Y')
} %}
{{ t('notifications.order_created', params) }}

Множественные формы

Русские множественные формы

В русском языке используется 3 формы множественного числа:

Форма Используется для Примеры
Форма 1 1, 21, 31, 41, ... 1 сообщение, 21 товар
Форма 2 2-4, 22-24, 32-34, ... 2 сообщения, 23 товара
Форма 3 0, 5-20, 25-30, ... 5 сообщений, 10 товаров

Настройка множественных форм

<!-- В файле ru.json -->
{
    "messages": {
        "count": [
            "У вас :count сообщение",      // n % 10 == 1 && n % 100 != 11
            "У вас :count сообщения",      // n % 10 in [2,3,4] && n % 100 not in [12,13,14]
            "У вас :count сообщений"       // остальные случаи
        ]
    },
    "products": {
        "count": [
            ":count товар",
            ":count товара", 
            ":count товаров"
        ]
    },
    "users": {
        "online": [
            ":count пользователь онлайн",
            ":count пользователя онлайн",
            ":count пользователей онлайн"
        ]
    }
}

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

<!-- Автоматическое склонение -->
{{ t('messages.count', 0) }}                  <!-- У вас 0 сообщений -->
{{ t('messages.count', 1) }}                  <!-- У вас 1 сообщение -->
{{ t('messages.count', 2) }}                  <!-- У вас 2 сообщения -->
{{ t('messages.count', 5) }}                  <!-- У вас 5 сообщений -->

<!-- В цикле -->
{% foreach users as user %}
    <div class="user-card">
        <h3>{{ user.name }}</h3>
        <p>{{ t('messages.count', user.message_count) }}</p>
        <p>{{ t('products.count', user.product_count) }}</p>
    </div>
{% endforeach %}

<!-- Динамический подсчет -->
{% set onlineCount = db('1752665380840')->countItems('users', {'online': 1}) %}
<p>{{ t('users.online', onlineCount) }}</p>

Конфигурация языков

Настройка в app.json

<!-- /src/config/app.json -->
{
    "locale": "ru",                           // Текущий язык
    "fallback_locale": "en",                  // Язык по умолчанию
    "available_locales": ["ru", "en", "uk", "zh", "de", "fr", "es"],
    "timezone": "Europe/Moscow",
    "auto_translate": true                    // Автоматический перевод
}

Системные переменные

<!-- Доступные переменные в шаблонах -->
{{ locale }}                                 <!-- ru -->
{{ fallback_locale }}                        <!-- en -->
{{ available_locales }}                     <!-- ["ru", "en", "uk", ...] -->

<!-- Проверка текущего языка -->
{% if locale == 'ru' %}
    <p>Добро пожаловать на русскую версию!</p>
{% elseif locale == 'en' %}
    <p>Welcome to the English version!</p>
{% endif %}

Переключение языков

Простой переключатель

<div class="language-switcher">
    {% foreach available_locales as lang %}
        <a href="/{{ lang }}" 
           class="{{ lang == locale ? 'active' : '' }}">
            {{ lang | upper }}
        </a>
    {% endforeach %}
</div>

Расширенный переключатель с названиями

{% set language_names = {
    'en': 'English',
    'ru': 'Русский',
    'uk': 'Українська',
    'zh': '中文',
    'de': 'Deutsch',
    'fr': 'Français',
    'es': 'Español'
} %}

{% set language_flags = {
    'en': '🇺🇸',
    'ru': '🇷🇺', 
    'uk': '🇺🇦',
    'zh': '🇨🇳',
    'de': '🇩🇪',
    'fr': '🇫🇷',
    'es': '🇪🇸'
} %}

<div class="language-menu">
    <button class="current-lang" onclick="toggleLangMenu()">
        {{ language_flags[locale] ?? '🌐' }}
        {{ language_names[locale] ?? locale | upper }}
        <i class="fas fa-chevron-down"></i>
    </button>
    
    <div class="lang-dropdown" id="langDropdown" style="display: none;">
        {% foreach available_locales as lang %}
            {% if lang != locale %}
                <a href="/{{ lang }}">
                    {{ language_flags[lang] ?? '🌐' }}
                    {{ language_names[lang] ?? lang | upper }}
                </a>
            {% endif %}
        {% endforeach %}
    </div>
</div>

<script>
function toggleLangMenu() {
    const dropdown = document.getElementById('langDropdown');
    dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
}

// Закрытие при клике вне меню
document.addEventListener('click', function(event) {
    const menu = document.querySelector('.language-menu');
    if (!menu.contains(event.target)) {
        document.getElementById('langDropdown').style.display = 'none';
    }
});
</script>

Обработка отсутствующих переводов

Fallback на основной язык

<!-- Если перевода нет в текущем языке, будет использован fallback_locale -->
{{ t('missing.key') }}                        <!-- Попытка ru -> en -> ключ -->

<!-- Значения по умолчанию -->
{{ t('undefined.key', {}, 'Значение по умолчанию') }}

<!-- Проверка существования перевода -->
{% if t_exists('some.key') %}
    {{ t('some.key') }}
{% else %}
    <span>Перевод не найден</span>
{% endif %}

Примеры использования

Форма регистрации

<form class="registration-form">
    <h2>{{ t('auth.register') }}</h2>
    
    <div class="form-group">
        <label>{{ t('form.email') }} *</label>
        <input type="email" name="email" required
               placeholder="{{ t('form.email_placeholder') }}">
    </div>
    
    <div class="form-group">
        <label>{{ t('form.password') }} *</label>
        <input type="password" name="password" required
               placeholder="{{ t('form.password_placeholder') }}">
    </div>
    
    <div class="form-group">
        <label>{{ t('form.confirm_password') }} *</label>
        <input type="password" name="password_confirmation" required>
    </div>
    
    <div class="form-check">
        <input type="checkbox" name="terms" value="1" required>
        <label>{{ t('form.agree_terms') }} *</label>
    </div>
    
    <button type="submit" class="btn-primary">
        {{ t('auth.register_button') }}
    </button>
    
    <p class="login-link">
        {{ t('auth.already_registered') }} 
        <a href="/login">{{ t('auth.login_here') }}</a>
    </p>
</form>

Соответствующий файл перевода

<!-- ru.json -->
{
    "auth": {
        "register": "Регистрация",
        "register_button": "Зарегистрироваться",
        "already_registered": "Уже зарегистрированы?",
        "login_here": "Войти"
    },
    "form": {
        "email": "Email",
        "email_placeholder": "example@domain.com",
        "password": "Пароль",
        "password_placeholder": "Минимум 6 символов",
        "confirm_password": "Подтверждение пароля",
        "agree_terms": "Согласен с условиями использования"
    }
}

<!-- en.json -->
{
    "auth": {
        "register": "Registration",
        "register_button": "Register",
        "already_registered": "Already registered?",
        "login_here": "Login"
    },
    "form": {
        "email": "Email",
        "email_placeholder": "example@domain.com",
        "password": "Password", 
        "password_placeholder": "Minimum 6 characters",
        "confirm_password": "Confirm Password",
        "agree_terms": "I agree to the terms of use"
    }
}

Страница товара

<div class="product-page">
    <h1>{{ product.name }}</h1>
    
    <div class="product-info">
        <div class="price">
            {% if product.sale_price %}
                <span class="sale-price">{{ product.sale_price | money('₽') }}</span>
                <span class="original-price">{{ product.price | money('₽') }}</span>
                {% set discount = ((product.price - product.sale_price) / product.price * 100) | round %}
                <span class="discount">{{ t('product.discount', {'percent': discount}) }}</span>
            {% else %}
                <span class="price">{{ product.price | money('₽') }}</span>
            {% endif %}
        </div>
        
        <div class="availability">
            {% if product.in_stock %}
                <span class="in-stock">{{ t('product.in_stock') }}</span>
                <p>{{ t('product.quantity_available', product.stock_count) }}</p>
            {% else %}
                <span class="out-of-stock">{{ t('product.out_of_stock') }}</span>
            {% endif %}
        </div>
        
        <div class="rating">
            <div class="stars">
                {% for i in 1..5 %}
                    <i class="fas fa-star {{ i <= product.rating ? 'active' : '' }}"></i>
                {% endfor %}
            </div>
            <span class="rating-text">
                {{ t('product.rating_reviews', {
                    'rating': product.rating,
                    'count': product.reviews_count
                }) }}
            </span>
        </div>
        
        <div class="actions">
            {% if product.in_stock %}
                <button class="btn-primary">{{ t('product.add_to_cart') }}</button>
                <button class="btn-secondary">{{ t('product.add_to_favorites') }}</button>
            {% else %}
                <button class="btn-notify">{{ t('product.notify_available') }}</button>
            {% endif %}
        </div>
    </div>
    
    <div class="product-description">
        <h3>{{ t('product.description') }}</h3>
        <div>{!! product.description !!}</div>
    </div>
</div>

Лучшие практики

Структурирование переводов

Рекомендации:
  • Группируйте связанные переводы по разделам (auth, forms, products)
  • Используйте понятные ключи на английском языке
  • Избегайте слишком глубокой вложенности (максимум 3 уровня)
  • Используйте параметры вместо конкатенации строк

Именование ключей

<!-- Хорошо -->
{
    "user": {
        "edit": "Редактировать профиль",
        "save": "Сохранить изменения",
        "cancel": "Отмена"
    }
}

<!-- Плохо -->
{
    "userProfileEditButton": "Редактировать профиль",
    "prof_save_btn": "Сохранить изменения"
}

Параметры и плейсхолдеры

<!-- Хорошо -->
{
    "welcome_user": "Добро пожаловать, :name!",
    "order_total": "Итого: :amount :currency",
    "items_found": "Найдено :count из :total товаров"
}

<!-- Плохо -->
{
    "welcome_start": "Добро пожаловать, ",
    "welcome_end": "!",
    "order_total_start": "Итого: "
}
Следующий шаг: Изучите продвинутые возможности переводов.