[Tutorial] Мультиязычный интерфейс
#1

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

Начнём с самого быстрого, действенного и не требующего дополнительных файлов, но требующего перекомпиляцию мода при внесении исправлений в текст - Массив.



pawn Код:
new Language[2][2][144] = { // 2 - кол-во языков, 2 - кол-во строк текста, 144 - максимальная длина текста
    {
        {" Text 1 Text 1 Text 1 "},
        {" Text 2 %s 2 Text 2 "}
    },
    {
        {" Текст 1 Текст 1 Текст 1 "},
        {" Текст 2 %s 2 Текст 2 "}
    }
};
При входе устанавливайте игроку язык интерфейса (в зависимости от национальности целевой аудитории... представим, что у нас она - русскоязычная):
pawn Код:
SetPVarInt(playerid, "Language", 1); // 0 - пусть будет по-умолчанию английский, а 1 - русский
Используется так:
pawn Код:
if(strcmp(cmdtext, "/rus1", true) == 0)
{
    SetPVarInt(playerid, "Language", 1); // для тестирования, у вас установка переменной должна быть в аккаунте игрока (при реге или с возможностью изменения)
    SendClientMessage(playerid, 0xFDE39DFF, Language[GetPVarInt(playerid, "Language")][0]);
    new string[128], PlayerName[MAX_PLAYER_NAME];
    GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
    format(string, sizeof(string), Language[GetPVarInt(playerid, "Language")][1], PlayerName);
    SendClientMessage(playerid, 0xFDE39DFF, string);
    return 1;
}
if(strcmp(cmdtext, "/eng1", true) == 0)
{
    SetPVarInt(playerid, "Language", 0);
    SendClientMessage(playerid, 0xFDE39DFF, Language[GetPVarInt(playerid, "Language")][0]);
    new string[128], PlayerName[MAX_PLAYER_NAME];
    GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
    format(string, sizeof(string), Language[GetPVarInt(playerid, "Language")][1], PlayerName);
    SendClientMessage(playerid, 0xFDE39DFF, string);
    return 1;
}
Второй вариант мультиязычность - это Дефайны. В этом случае мы присваиваем константам определённый текст для каждого языка.



Самый сложный пример:
pawn Код:
#define RUS_TEXT_ONE "Текст текст текст"
#define RUS_TEXT_TWO "Текст %s текст"
#define ENG_TEXT_ONE "Text text text"
#define ENG_TEXT_TWO "Text %s text"
Опять введём переменную, отвечающую за язык и посмотрим пример использования:
pawn Код:
SetPVarInt(playerid, "Language", 0);
Самый простой вариант:
pawn Код:
if(GetPVarInt(playerid, "Language") == 1) return SendClientMessage(playerid, 0xFDE39DFF, RUS_TEXT_ONE);
else SendClientMessage(playerid, 0xFDE39DFF, ENG_TEXT_ONE);
Сложный вариант (с внутренним форматированием):
pawn Код:
if(strcmp(cmdtext, "/rus2", true) == 0)
{
    SetPVarInt(playerid, "Language", 1);
    if(GetPVarInt(playerid, "Language") == 1)
    {
        SendClientMessage(playerid, 0xFDE39DFF, RUS_TEXT_ONE);
        new string[128], PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string, sizeof(string), RUS_TEXT_TWO, PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string);
    }
    else
    {
        SendClientMessage(playerid, 0xFDE39DFF, ENG_TEXT_ONE);
        new string[128], PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string, sizeof(string), ENG_TEXT_TWO, PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string);
    }
    return 1;
}
if(strcmp(cmdtext, "/eng2", true) == 0)
{
    SetPVarInt(playerid, "Language", 0);
    if(GetPVarInt(playerid, "Language") == 1)
    {
        SendClientMessage(playerid, 0xFDE39DFF, RUS_TEXT_ONE);
        new string[128], PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string, sizeof(string), RUS_TEXT_TWO, PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string);
    }
    else
    {
        SendClientMessage(playerid, 0xFDE39DFF, ENG_TEXT_ONE);
        new string[128], PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string, sizeof(string), ENG_TEXT_TWO, PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string);
    }
    return 1;
}
Как вы понимаете, нерентабельно для каждого вывода текста ставить условие по языку. В вдруг их не 2, а 10? Код будет заспамлен условиями. Поэтому есть вариант попроще (с дополнительными функциями): http://pawno-info.ru/showthread.pawn?t=193903 (не мешало бы объединить темы).

Однако есть и более простой и действенный способ, который был реализован [RSAH]SeriouS в его моде Premium A/D - Файлы. Поскольку исходников мода не выкладывалось, реализацию я сделал собственную. Не могу утверждать, что она лучше оригинала (в моде), но попробуйте.



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

pawn Код:
SetPVarString(playerid, "Language", "russian.lng");
Далее в нужный момент времени надо показать игроку необходимый текст. На mxINI это будет выглядеть так:
pawn Код:
new string[128], string2[128], languagefile[32];
GetPVarString(playerid, "Language", languagefile, sizeof(languagefile));
new iniFile = ini_openFile(languagefile);
if(iniFile < 0) return printf("Не удалось найти файл с переводом для данного языка %s.", languagefile);
ini_getString(iniFile, "Text_0001", string, sizeof(string));
ini_getString(iniFile, "Text_0002", string2, sizeof(string2));
ini_closeFile(iniFile);
SendClientMessage(playerid, 0xFDE39DFF, string);
new PlayerName[MAX_PLAYER_NAME];
GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
format(string2, sizeof(string2), string2, PlayerName); // поскольку текст содержит маркер форматирования %s, вставляем туда имя игрока
SendClientMessage(playerid, 0xFDE39DFF, string2);
Пример подобран для показа способов форматирования получаемого текста. Тот же код на Dini:
pawn Код:
new string[128], languagefile[32];
GetPVarString(playerid, "Language", languagefile, sizeof(languagefile));
SendClientMessage(playerid, 0xFDE39DFF, dini_Get(languagefile, "Text_0001"));
new PlayerName[MAX_PLAYER_NAME];
GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
format(string, sizeof(string), dini_Get(languagefile, "Text_0002"), PlayerName);
SendClientMessage(playerid, 0xFDE39DFF, string);
Содержимое файла russian.lng:
pawn Код:
Text_0001 = (ИНФО) Добро пожаловать на наш сервер!
Text_0002 = (ИНФО) Общие настройки в профиле "%s" были загружены.
Содержимое файла english.lng:
pawn Код:
Text_0001= (INFO) Welcome to our server!
Text_0002= (INFO) General settings from profile "%s" have been loaded.
Урок написан с надеждой на то, что те, кто будет его читать, уже работали с mxINI или Dini. В принципе, ничего сложного в них нет.


[spoiler=Полный листинг кода скрипта с тестом скорости]
pawn Код:
#include <a_samp>
#include <mxINI>
#include <Dini>
#define RUS_TEXT_ONE "Текст текст текст"
#define RUS_TEXT_TWO "Текст %s текст"
#define ENG_TEXT_ONE "Text text text"
#define ENG_TEXT_TWO "Text %s text"
new Language[2][2][128] = {
    {
        {" Text 1 Text 1 Text 1 "},
        {" Text 2 %s 2 Text 2 "}
    },
    {
        {" Текст 1 Текст 1 Текст 1 "},
        {" Текст 2 %s 2 Текст 2 "}
    }
};
public OnPlayerConnect(playerid)
{
    SendClientMessage(playerid, 0xFF0000FF, "Type /rus to change language of interface");
    return 1;
}
public OnPlayerCommandText(playerid, cmdtext[])
{
    if(strcmp(cmdtext, "/rus1", true) == 0)
    {
        SetPVarInt(playerid, "Language", 1);
        SendClientMessage(playerid, 0xFDE39DFF, Language[GetPVarInt(playerid, "Language")][0]);
        new string[128], PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string, sizeof(string), Language[GetPVarInt(playerid, "Language")][1], PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string);
        return 1;
    }
    if(strcmp(cmdtext, "/eng1", true) == 0)
    {
        SetPVarInt(playerid, "Language", 0);
        SendClientMessage(playerid, 0xFDE39DFF, Language[GetPVarInt(playerid, "Language")][0]);
        new string[128], PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string, sizeof(string), Language[GetPVarInt(playerid, "Language")][1], PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string);
        return 1;
    }
    if(strcmp(cmdtext, "/rus2", true) == 0)
    {
        SetPVarInt(playerid, "Language", 1);
        if(GetPVarInt(playerid, "Language") == 1)
        {
            SendClientMessage(playerid, 0xFDE39DFF, RUS_TEXT_ONE);
            new string[128], PlayerName[MAX_PLAYER_NAME];
            GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
            format(string, sizeof(string), RUS_TEXT_TWO, PlayerName);
            SendClientMessage(playerid, 0xFDE39DFF, string);
        }
        else
        {
            SendClientMessage(playerid, 0xFDE39DFF, ENG_TEXT_ONE);
            new string[128], PlayerName[MAX_PLAYER_NAME];
            GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
            format(string, sizeof(string), ENG_TEXT_TWO, PlayerName);
            SendClientMessage(playerid, 0xFDE39DFF, string);
        }
        return 1;
    }
    if(strcmp(cmdtext, "/eng2", true) == 0)
    {
        SetPVarInt(playerid, "Language", 0);
        if(GetPVarInt(playerid, "Language") == 1)
        {
            SendClientMessage(playerid, 0xFDE39DFF, RUS_TEXT_ONE);
            new string[128], PlayerName[MAX_PLAYER_NAME];
            GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
            format(string, sizeof(string), RUS_TEXT_TWO, PlayerName);
            SendClientMessage(playerid, 0xFDE39DFF, string);
        }
        else
        {
            SendClientMessage(playerid, 0xFDE39DFF, ENG_TEXT_ONE);
            new string[128], PlayerName[MAX_PLAYER_NAME];
            GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
            format(string, sizeof(string), ENG_TEXT_TWO, PlayerName);
            SendClientMessage(playerid, 0xFDE39DFF, string);
        }
        return 1;
    }
    if(strcmp(cmdtext, "/rus", true) == 0)
    {
        SetPVarString(playerid, "Language", "russian.lng");
        new string[128], string2[128], languagefile[32];
        GetPVarString(playerid, "Language", languagefile, sizeof(languagefile));
        new iniFile = ini_openFile(languagefile);
        if(iniFile < 0) return printf("Не удалось найти файл с переводом для данного языка %s.", languagefile);
        ini_getString(iniFile, "Text_0001", string, sizeof(string));
        ini_getString(iniFile, "Text_0002", string2, sizeof(string2));
        ini_closeFile(iniFile);
        SendClientMessage(playerid, 0xFDE39DFF, string);
        new PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string2, sizeof(string2), string2, PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string2);
        return 1;
    }
    if(strcmp(cmdtext, "/eng", true) == 0)
    {
        SetPVarString(playerid, "Language", "english.lng");
        new string[128], languagefile[32];
        GetPVarString(playerid, "Language", languagefile, sizeof(languagefile));
        SendClientMessage(playerid, 0xFDE39DFF, dini_Get(languagefile, "Text_0001"));
        new PlayerName[MAX_PLAYER_NAME];
        GetPlayerName(playerid, PlayerName, sizeof(PlayerName));
        format(string, sizeof(string), dini_Get(languagefile, "Text_0002"), PlayerName);
        SendClientMessage(playerid, 0xFDE39DFF, string);
        return 1;
    }
    if(strcmp(cmdtext, "/test", true) == 0)
    {
        new time = gettime();
        for(new i = 0; i < 100000; i++) OnPlayerCommandText(playerid, "/rus");
        printf("Time 1 (MXini) = %d", gettime() - time);
        time = gettime();
        for(new i = 0; i < 100000; i++) OnPlayerCommandText(playerid, "/eng");
        printf("Time 2 (Dini) = %d", gettime() - time);
        return 1;
    }
    return 0;
}
[/spoiler]

Для одной форматируемой строки:



Для 2-х строк (одна неформатируемая, другая форматируемая):



Разница между Dini и mxINI в том, что у первого открытие файла и чтение ключа происходит в самой функции получения информации, а у mxINI надо всё делать самому. Но во втором случае можно прочитать сразу все строки и записать их в переменные. Если текст на сервере относится совершенно к разным моментам игры, то я посоветую использовать первый вариант (Dini). И хочу спасибо сказать Stepashka за наводку на принцип форматирования скрытого маркера.

Автор: OKStyle
Reply
#2

Упаковка памяти плохая, много свободных ячеек будет выделяться просто так. Есть один вариант решения, нужно время чтобы проверить.
(Теория оправдалась, скоро выложу инклудик.)
Reply
#3

Использовать MxINI/Dini и подобное не оправдано. Да и вообще использовать файлы таким образом - плохая идея, лучше при запуске прочитать файлы и записать всё в память.

Я реализовывал систему с property (ссылка), которая при запуске сервера читает файл и записывает всё в память. Правда я не делал работу несколько языков одновременно, ибо считаю это плохой идеей. В принципе, можно добавить одновременную работу нескольких языков, может займусь. Медленнее чем массивы, но красиво. Вот если бы Pawn был новее, то можно было бы реализовать всё ещё лучше.
Reply
#4

Чем не устраивает реализация в виде плагина? Когда-то тут выложил такой вариант: клик

Доступ к строчкам:
PHP код:
public imes_simple_single(playeridcolorstr[])
{
    new 
imes[256];
    
imessage(imesstrgPlayerLang[playerid]);
    
SendClientMessage(playeridcolorimes);
}
stock some_function()
{
   
//...
        //простой вариант:
        
imes_simple_single(playerid0xFFCC00FF"SOME_SIMPLE_MULTISTRING");
  
   
//...
        //более сложный вариант:
        
imessage(imes"SOME_COMPLEX_MULTISTRING"gPlayerLang[playerid]);
        
format(messizeof(mes), imessome_value_or_string);
        
SendClientMessage(playerid0xFFCC00FFmes);
   
//...

1) простота доступа;
2) редактирование без перекомпиляции мода, даже без перезапуска сервера и перезагрузки плагина;
3) юникод;
4) рациональное использование памяти;
5) ну и, разумеется, максимальная скорость доступа без посредников (файл с переводами полностью загружается в память).
Reply
#5

У тебя тот же самый вариант с дефайнами.
Reply
#6

Только внешне похож, разница - см. пункты 2), 3), 4), 5).
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)