Статьи

Создание вашего стартапа с помощью PHP: локализация с помощью I18n

Конечный продукт
Что вы будете создавать

Это четвертая часть серии « Построение стартапа на PHP» на Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе мы будем публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, которые вы можете изучить. Мы также будем решать вопросы, связанные со стартапом, по мере их возникновения.

В этом руководстве я хотел сделать шаг назад и добавить поддержку интернационализации I18n в наше приложение, прежде чем мы будем создавать все больше и больше кода. Согласно Википедии , I18n является нумеронимом:

18 обозначает число букв между первым i и последним n в интернационализации , использование, придуманное в DEC в 1970-х или 80-х.

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

Весь код для Meeting Planner написан на Yii2 Framework для PHP, который имеет встроенную поддержку I18n. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 на Tuts +.

Просто напоминание, я участвую в комментариях ниже. Мне особенно интересно, если у вас есть другие подходы или дополнительные идеи, или вы хотите предложить темы для будущих уроков.

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

В нашем случае Yii Framework предоставляет встроенную поддержку для I18n, поэтому встроенную поддержку для I18n относительно легко с самого начала, а добавление занимает больше времени.

I18n работает, заменяя все ссылки на текст, отображаемый пользователю вызовами функций, которые обеспечивают перевод при необходимости.

Например, вот как выглядят имена полей атрибутов в модели Place до I18n:

1
2
3
4
5
6
7
public function attributeLabels()
{
    return [
      ‘id’ => ‘ID’,
       ‘name’ => ‘Name’,
       ‘place_type’ => ‘Place Type’,
       …

Предоставление переведенных версий кода станет очень сложным. Нетехнические переводчики должны будут переводить код на месте, вероятно, нарушая синтаксис.

Вот как выглядит тот же код с I18n:

1
2
3
4
5
6
public function attributeLabels()
   {
       return [
         ‘id’ => Yii::t(‘frontend’, ‘ID’),
          ‘name’ => Yii::t(‘frontend’, ‘Name’),
          ‘place_type’ => Yii::t(‘frontend’, ‘Place Type’),

Yii:t() — это вызов функции, который проверяет, какой язык выбран в данный момент, и отображает соответствующую переведенную строку. 'frontend' относится к разделу нашего приложения. При желании переводы могут быть организованы в соответствии с различными категориями. Но где эти переведенные строки появляются?

Язык по умолчанию, в данном случае английский, записывается в код, как показано выше. Файлы языковых ресурсов — это списки массивов строк, ключом которых является текст языка по умолчанию, например, «Тип места», и каждый файл предоставляет переведенные текстовые значения для соответствующего языка.

Вот пример нашего полного файла перевода на испанский язык с кодом «es». Функция Yii:t() использует этот файл, чтобы найти соответствующий перевод для отображения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
/**
 * Message translations.
 *
 * This file is automatically generated by ‘yii message’ command.
 * It contains the localizable messages extracted from source code.
 * You may modify this file by translating the extracted messages.
 *
 * Each array element represents the translation (value) of a message (key).
 * If the value is empty, the message is considered as not translated.
 * Messages that no longer need translation will have their translations
 * enclosed between a pair of ‘@@’ marks.
 *
 * Message string can be used with plural forms format.
 * of the guide for details.
 *
 * NOTE: this file must be saved in UTF-8 encoding.
 */
return [
    ‘Add Current Location’ => ‘Agregar ubicación actual’,
    ‘Add a Google {modelClass}’ => ‘Añadir un Google {modelClass}’,
    ‘Created By’ => ‘Creado por’,
    ‘Full Address’ => ‘Dirección completa’,
    ‘Google Place ID’ => ‘Google Place ID’,
    ‘Name’ => ‘Nombre’,
    ‘Notes’ => ‘Notas’,
    ‘Place Type’ => ‘Place Tipo’,
    ‘Places’ => ‘Lugares’,

Хотя это выглядит трудоемким, Yii предоставляет сценарии для автоматизации создания и организации этих файлов.

Отделяя текст от кода, мы облегчаем для нетехнических многоязычных экспертов перевод наших приложений для нас — без нарушения кода.

I18n также предлагает специализированные функции для перевода времени, валюты, множественного числа и др. Я не буду вдаваться в детали этого в этом уроке.

К сожалению, документация по Yii2 для I18n еще не очень наглядна — и было трудно найти рабочие, пошаговые примеры. К счастью для вас, я проведу вас через то, что я узнал, изучая документы и сеть. Я нашел пример Code Ninja I18n и Yii2 Definitive Guide on I18n полезным, и участник Yii Александр Макаров также предложил мне некоторую помощь.

Мы используем расширенный шаблон Yii2 для Планировщика собраний. Это создает два приложения Yii в нашей кодовой базе: внешний и внутренний. И это создает общую область для моделей, совместно используемых обоими приложениями. Конфигурационные файлы Yii загружаются при каждом запросе страницы. Мы будем использовать сценарии сообщений Yiin I18n для создания файла конфигурации для I18n в common/config пути common/config .

Из нашего корня кодовой базы мы запустим скрипт Yii message/config :

1
./yii message/config @common/config/i18n.php

Это создает следующий шаблон файла, который мы можем настроить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php
 
return [
    // string, required, root directory of all source files
    ‘sourcePath’ => __DIR__,
    // array, required, list of language codes that the extracted messages
    // should be translated to.
    ‘languages’ => [‘de’],
    // string, the name of the function for translating messages.
    // Defaults to ‘Yii::t’.
    // translated.
    // multiple function names.
    ‘translator’ => ‘Yii::t’,
    // boolean, whether to sort messages by keys when merging new messages
    // with the existing ones.
    // messages will be separated from the old (translated) ones.
    ‘sort’ => false,
    // boolean, whether to remove messages that no longer appear in the source code.
    // Defaults to false, which means each of these messages will be enclosed with a pair of ‘@@’ marks.
    ‘removeUnused’ => false,
    // array, list of patterns that specify which files/directories should NOT be processed.
    // If empty or not set, all files/directories will be processed.
    // A path matches a pattern if it contains the pattern string at its end.
    // ‘/a/b’ will match all files and directories ending with ‘/a/b’;
    // the ‘*.svn’ will match all files and directories whose name ends with ‘.svn’.
    // and the ‘.svn’ will match all files and directories named exactly ‘.svn’.
    // Note, the ‘/’ characters in a pattern matches both ‘/’ and ‘\’.
    // See helpers/FileHelper::findFiles() description for more details on pattern matching rules.
    ‘only’ => [‘*.php’],
    // array, list of patterns that specify which files (not directories) should be processed.
    // If empty or not set, all files will be processed.
    // Please refer to «except» for details about the patterns.
    // If a file/directory matches both a pattern in «only» and «except», it will NOT be processed.
    ‘except’ => [
        ‘.svn’,
        ‘.git’,
        ‘.gitignore’,
        ‘.gitkeep’,
        ‘.hgignore’,
        ‘.hgkeep’,
        ‘/messages’,
    ],
 
    // ‘php’ output format is for saving messages to php files.
    ‘format’ => ‘php’,
    // Root directory containing message translations.
    ‘messagePath’ => __DIR__ .
    // boolean, whether the message file should be overwritten with the merged messages
    ‘overwrite’ => true,
 
 
    /*
    // ‘db’ output format is for saving messages to database.
    ‘format’ => ‘db’,
    // Connection component to use.
    ‘db’ => ‘db’,
    // Custom source message table.
    // ‘sourceMessageTable’ => ‘{{%source_message}}’,
    // Custom name for translation message table.
    // ‘messageTable’ => ‘{{%message}}’,
    */
 
    /*
    // ‘po’ output format is for saving messages to gettext po files.
    ‘format’ => ‘po’,
    // Root directory containing message translations.
    ‘messagePath’ => __DIR__ .
    // Name of the file that will be used for translations.
    ‘catalog’ => ‘messages’,
    // boolean, whether the message file should be overwritten with the merged messages
    ‘overwrite’ => true,
    */
];

Я настраиваю свой файл. Я перемещаю messagePath вверх и настраиваю sourcePath и messagePath . Я также указываю языки, которые я хочу, чтобы моя заявка поддерживала помимо английского, в данном случае это испанский и немецкий, es и de. Вот список всех кодов языка I18n .

1
2
3
4
5
6
7
8
return [
   // string, required, root directory of all source files
   ‘sourcePath’ => __DIR__.
   // Root directory containing message translations.
   ‘messagePath’ => __DIR__ .
   // array, required, list of language codes that the extracted messages
   // should be translated to.
   ‘languages’ => [‘es’,’de’],

На следующем шаге мы запустим скрипт извлечения Yii, который будет сканировать весь код в дереве sourcePath чтобы сгенерировать строковые файлы по умолчанию для всех меток, используемых в нашем коде. Я настраиваю sourcePath для сканирования всего дерева кода. Я настраиваю messagePath для генерации результирующих файлов в common/messages .

1
./yii message/extract @common/config/i18n.php

Вы увидите, как Yii сканирует все ваши файлы кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Extracting messages from /Users/Jeff/Sites/mp/frontend/models/Place.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/models/PlaceGPS.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/models/PlaceSearch.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/models/ResetPasswordForm.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/models/SignupForm.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/layouts/main.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/meeting/_form.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/meeting/_search.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/meeting/create.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/meeting/index.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/meeting/update.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/meeting/view.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/_form.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/_formGeolocate.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/_formPlaceGoogle.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/_search.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/create.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/create_geo.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/create_place_google.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/index.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/locate.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/update.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/place/view.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/about.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/contact.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/error.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/index.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/login.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/requestPasswordResetToken.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/resetPassword.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/views/site/signup.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/web/index-test.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/web/index.php…
Extracting messages from /Users/Jeff/Sites/mp/frontend/widgets/Alert.php…

Когда он завершится, вы увидите что-то вроде этого в вашей кодовой базе:

Планировщик собраний I18n Файлы сообщений и пути

В общем конфигурационном файле common/config/main.php мы расскажем Yii о нашей новой языковой поддержке. Я сделаю испанский своим языком по умолчанию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
return [
    ‘vendorPath’ => dirname(dirname(__DIR__)) .
    ‘language’ => ‘es’, // spanish
    ‘components’ => [
        ‘cache’ => [
            ‘class’ => ‘yii\caching\FileCache’,
        ],
        ‘i18n’ => [
            ‘translations’ => [
                ‘frontend*’ => [
                    ‘class’ => ‘yii\i18n\PhpMessageSource’,
                    ‘basePath’ => ‘@common/messages’,
                ],
                ‘backend*’ => [
                    ‘class’ => ‘yii\i18n\PhpMessageSource’,
                    ‘basePath’ => ‘@common/messages’,
                ],
            ],
        ],
    ],
];

Но это еще не все. Мы должны сделать наш код I18n осведомленным.

Во второй части этой серии « Создание стартапа с помощью PHP: требования к функциям и проектирование баз данных» мы использовали потрясающий генератор кода Yii, Gii, для генерации наших моделей, контроллеров и представлений. Но мы не активировали I18n, поэтому во весь наш код встроены текстовые строки. Давайте переделаем это.

Мы возвращаемся к Gii, вероятно, http: // localhost: 8888 / mp / gii в вашем браузере, и перезапускаем модель и генераторы контроллера с активированным I18n.

Примечание. Если вы не отставали от третьей части нашей серии, возможно, вам придется записать различия между каждым файлом и вручную переместить код. Или, может быть, проще всего заменить ваш код на Github-репозиторий для этого урока, связанный в правом верхнем углу.

Вот пример генерации кода модели Meeting с активированным I18n. Обратите внимание, что мы указываем «внешний интерфейс» для нашей категории сообщений. Мы помещаем все наши текстовые строки внешнего интерфейса в один файл категории внешнего интерфейса.

Планировщик встреч I18n Gii Model Generator

Давайте сделаем то же самое для генерации CRUD для контроллеров и представлений:

Планировщик встреч I18n Генератор сообщений CRUD с Gii

Если вы просматриваете сгенерированный код в моделях, контроллерах и представлениях, вы увидите, что текстовые строки заменены функцией Yii:t('frontend',...) :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
 
use yii\helpers\Html;
use yii\grid\GridView;
 
/* @var $this yii\web\View */
/* @var $searchModel frontend\models\PlaceSearch */
/* @var $dataProvider yii\data\ActiveDataProvider */
 
$this->title = Yii::t(‘frontend’, ‘Places’);
$this->params[‘breadcrumbs’][] = $this->title;
?>
<div class=»place-index»>
 
    <h1><?= Html::encode($this->title) ?></h1>
    <?php // echo $this->render(‘_search’, [‘model’ => $searchModel]);
 
    <p>
         <?= Html::a(Yii::t(‘frontend’, ‘Create {modelClass}’, [
           ‘modelClass’ => ‘Place’,
        ]), [‘create’], [‘class’ => ‘btn btn-success’]) ?>
         
        <?= Html::a(Yii::t(‘frontend’,’Add Current Location’), [‘create_geo’], [‘class’ => ‘btn btn-success’]) ?>
        <?= Html::a(Yii::t(‘frontend’,’Add a Google {modelClass}’,[
           ‘modelClass’ => ‘Place’
        ]), [‘create_place_google’], [‘class’ => ‘btn btn-success’]) ?>
    </p>

Взгляните на наш испанский файл сообщений, /common/messages/es/frontend.php . Это длинный список значений пустого массива:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
return [
    ‘Add Current Location’ => »,
    ‘Add a Google {modelClass}’ => »,
    ‘Are you sure you want to delete this item?’
    ‘Create’ => »,
    ‘Create {modelClass}’ => »,
    ‘Created At’ => »,
    ‘Created By’ => »,
    ‘Delete’ => »,
    ‘Full Address’ => »,
    ‘Google Place ID’ => »,
    ‘ID’ => »,
    ‘Meeting Type’ => »,
    ‘Meetings’ => »,
    ‘Message’ => »,
    ‘Name’ => »,
    ‘Notes’ => »,
    ‘Owner ID’ => »,
    ‘Place Type’ => »,
    ‘Places’ => »,
    ‘Reset’ => »,
    ‘Search’ => »,
    ‘Slug’ => »,
    ‘Status’ => »,
    ‘Update’ => »,
    ‘Update {modelClass}: ‘ => »,
    ‘Updated At’ => »,
    ‘Vicinity’ => »,
    ‘Website’ => »,
];

Для заполнения наших переводов на испанский язык для этого урока я буду использовать Google Translate . Хитрый, а?

Планировщик собраний I18n Использование Google Translator для заполнения файлов сообщений

Затем мы сделаем некоторые вставки с этими переводами обратно в файл сообщений.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
return [
    ‘Add Current Location’ => ‘Agregar ubicación actual’,
    ‘Add a Google {modelClass}’ => ‘Añadir un Google {modelClass}’,
    ‘Created By’ => ‘Creado por’,
    ‘Full Address’ => ‘Dirección completa’,
    ‘Google Place ID’ => ‘Google Place ID’,
    ‘Name’ => ‘Nombre’,
    ‘Notes’ => ‘Notas’,
    ‘Place Type’ => ‘Place Tipo’,
    ‘Places’ => ‘Lugares’,
    ‘Slug’ => ‘Slug’,
    ‘Vicinity’ => ‘Alrededores’,
    ‘Website’ => ‘Sitio Web’,
    ‘Are you sure you want to delete this item?’
    ‘Create’ => ‘Crear’,
    ‘Create {modelClass}’ => ‘Crear Lugar’,
    ‘Created At’ => ‘Creado El’,
    ‘Delete’ => ‘Eliminar’,
    ‘ID’ => ‘ID’,
    ‘Meeting Type’ => ‘Tipo de Reunión’,
    ‘Meetings’ => ‘Encuentros’,
    ‘Message’ => ‘Mensaje’,
    ‘Owner ID’ => ‘Propietario ID’,
    ‘Reset’ => ‘Reset’,
    ‘Search’ => ‘Buscar’,
    ‘Status’ => ‘Estado’,
    ‘Update’ => ‘Actualizar’,
    ‘Update {modelClass}: ‘ => ‘Actualizar Lugar’,
    ‘Updated At’ => ‘Actualización A’,
];

Когда мы посетим индексную страницу Place, вы увидите испанскую версию — хорошо, а?

Планировщик встреч I18n Испанская страница Указатель мест

Обратите внимание, что панель навигации остается на английском языке — это потому, что сценарий Message / Extract не взял определения массива навигации Bootstrap и не преобразовал их в Yii:t() . Мы сделаем это вручную. Кроме того, обратите внимание, что текст Home и paging был переведен автоматически — кодовая база Yii включает языковые переводы для этих стандартных строк.

Вот форма создания места :

Планировщик встреч I18n Испанский Создать форму

Если я хочу переключиться обратно на английский, я просто изменяю файл конфигурации, /common/main.php , обратно на английский:

1
2
3
4
5
<?php
return [
    ‘vendorPath’ => dirname(dirname(__DIR__)) .
    ‘language’ => ‘en’, // english
// ‘language’ => ‘es’, // spanish
Планировщик встреч Создать место на английском языке

В процессе работы вы также заметите, что замена строк в JavaScript имеет свои сложности. Я не исследовал это сам, но расширение Yii 1.x JsTrans может предоставить полезную рекомендацию для поддержки этого.

В конечном счете, мы можем захотеть перевести наше приложение на несколько языков. Я опубликовал запрос на функцию Yii, чтобы расширить скрипт Message / Extract для использования Google Translate API для автоматизации этого процесса . Я также попросил Tuts + разрешить мне написать учебник об этом, так что следите за обновлениями. Конечно, это просто обеспечивает базовый перевод. Вы можете нанять профессиональных переводчиков для настройки файлов впоследствии.

Некоторые приложения позволяют пользователям выбирать свой родной язык, чтобы при входе в систему пользовательский интерфейс автоматически переводил их. В Yii установка переменной $app->language делает это:

Другие приложения, такие как JScrambler.com ниже, используют URL-путь для переключения языков. Пользователь просто щелкает языковой префикс, который он хочет, например, «FR» , и приложение автоматически переводится:

JScrambler Динамические языковые пути

Примечание. Посмотрите мою страницу инструктора Tuts +, чтобы узнать о предстоящем уроке о JScrambler — это довольно полезный сервис. Возможно, оно уже появилось к тому времени, как вы это прочитали.

Менеджер URL-адресов Yii также может обеспечить этот тип функциональности . Вероятно, я буду реализовывать эти функции для Meeting Planner в следующем уроке.

Я надеюсь, что вы узнали что-то новое с этим уроком. Раньше я использовал I18n с Rails, но впервые применил его с PHP. Следите за будущими уроками в нашей серии «Построение стартапа с помощью PHP» — впереди много интересных функций.

Пожалуйста, не стесняйтесь добавлять свои вопросы и комментарии ниже; Я обычно участвую в обсуждениях. Вы также можете связаться со мной в Twitter @reifman или написать мне напрямую.