Основы переводов
Полное руководство по системе переводов и локализации в 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": "Итого: "
}
Следующий шаг: Изучите продвинутые возможности переводов.