Инструменты пользователя

Инструменты сайта


lm3:ce:t_multi_lang

Туториал: Мультиязычность

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

В таких проектах при разработке на Лямбда-Мю 3 делается следующее:

  • текстовые данные создаются в таблице (например, Google Таблицы)
  • создается специальный скрипт в такой таблице, который позволяет выгружать данные в lua-нотации
  • данные выгружаются и располагаются в папке проекта
  • основные скрипты платформы подхватывают данные и оперируют с ними

Такой подход удобен, и данные легко обновлять.

Пример реализации

Платформа Лямбда-Мю 3 может использоваться как back end сервер веб-проекта. Одним из таких проектов является «Сомнология B2C» – мобильное приложение на Android.

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

Репозитарий

Файловая структура проекта представлена ниже.

Необходимые скрипты туториала находятся в репозитарии lm3.examples/multi_lang.

Чтобы запустить платформу скачайте бинарный файл и необходимые библиотеки из репозитария lm3.engine.ce и скопируйте в корень проекта.

Исходные данные

Проект «Сомнология B2C»:

  • back end: платформа Лямбда-Мю 3 [server]
  • front end: Android-приложение [client]

Для отладки решения будут использоваться следующие версии:

  • [client] : Android-приложение с вырезанным пользовательским интерфейсом и бизнес-логикой
  • [server] : платформа Лямбда-Мю 3 с вырезанной бизнес-логикой

Цель проекта

Доработать взаимодействие между [client] и [server].

Требования

Требованием к проекту является минимальные изменения исходного проекта [client] и [server]. Требование достигается использованием того же протокола, который разработан в исходном проекте.

Авторизация:

Запрос:

{
  { Object = "Auth", Command = "Set", Name = "UserName", Data = <string> },
  { Object = "Auth", Command = "Set", Name = "UserMail", Data = <string> },
  { Object = "Settings", Command = "Event", Name = "DoChangeLang", Data = <string> },
}

Ответ:

{ { Object = "Auth", Command = "Value", Name = "TaskObject", Data = <string> } }

Запрос списка курсов:

Запрос:

{ { Object = "Info", Command = "Event", Name = "CourceList" } }

Ответ:

{ { Object = "Info", Command = "Value", Name = "CourceList", Data = <array[table[CourceID:string, Name:string, Description:string]]> } }

Смена локализации:

Запрос:

{ { Object = "Auth", Command = "Value", Name = "TaskObject", Data = <string> } }

Файловая структура проекта

Взаимосвязи

Объекты основной логической машины:

  • TCP-server server: прием и передача данных клиенту
  • DataPack packer: распаковка и запаковка данных клиенту
  • Auth: авторизация клиента; вызов создания ЛМ; связь клиента с соответвующей ЛМ
  • Сlients (наследник LMList): управление ЛМ

Объекты логических машин:

  • Info: обработка запрос списка курсов
  • Settings: обработка запроса на смену локализации

Скрипты

main / start.lua

lm3:Log("Starting server")
 
lm3:LoadLibrary( {Alias = "sys", FileName = "lm3system"} )
lm3:Include("main_lib.lua")
 
lm3:CreateLObject( { LibAlias = "sys", Type = "TCPServer", Name = "server" } )
server:SetAttr("SocCount", lmconf.MaxClientCount)
 
lm3:CreateLObject( { LibAlias = "sys", Type = "DataPack", Name = "packer" } )
packer:SetAttr("BufCount", lmconf.MaxClientCount)
 
lm3:CreateObject( { Type = "LMList", Name = "clients" } )
 
init_Auth("Auth")
 
lm3:AddHandler("OnStart", function(o, ev)
    -- проверка конфигурации ЛМ
    if clients:DoPrepare(lmconf.BasePath.."client_lm/config.lua") == false then
        lm3:Log("client_lm prepare failed")
    end
end)
 
Auth:AddHandler("OnReply", function(o, ev)
  lm3:Log("Auth: OnReply: tag: "..tostring(ev.Tag).." : "..lm3:DataToString(ev.Data))
end)
 
-- связи объектов
server:Connect("OnConnect", "Auth", "DoConnect")
server:Connect("OnDisconnect", "Auth", "DoDisconnect")
server:Connect("OnIncoming", "packer", "DoUnpack")
packer:Connect("OnUnpack", "Auth", "DoIncoming")
Auth:Connect("OnReply", "packer", "DoPackTagged")
packer:Connect("OnPack", "server", "DoSend")
Auth:Connect("OnStartClientLM", "clients", "DoStart")
Auth:Connect("OnStopClientLM", "clients", "DoStop")
Auth:Connect("OnClientEvent", "clients", "DoEvent")
clients:Connect("OnEvent", "Auth", "DoClientEvent")

main / main_lib.lua

-- объект Auth
-- авторизирует клиента
-- вызывает создание ЛМ
-- связывает клиента с соответвующей ЛМ
function init_Auth(_name)
    local obj = lm3:CreateObject( { Type = "Base", Name = _name } )
 
    obj.ClientList = {}
    for i = 1, lmconf.MaxClientCount do
        obj.ClientList[i] = {
            isConnected = false,
            UserName = "",
            UserMail = "",
            LMName = "",
        }
    end
 
    function obj.TagFromLMName(_name)
        return tonumber(_name:sub(5))
    end
 
    -- подключение нового клиента
    -- старт ЛМ
    obj:AddInEvent("DoConnect", "int", function(o, ev)
        lm3:Log("New client connected:"..tostring(ev))
        obj.ClientList[ev].isConnected = true
        obj.ClientList[ev].UserName = "N/A"
        obj.ClientList[ev].UserMail = "N/A"
        obj.ClientList[ev].LMName = "clm_"..tostring(ev)
        lm3:Log("Start client LM...")
        if obj:OnStartClientLM( { Name = obj.ClientList[ev].LMName } ) == true then
            lm3:Log("Start client LM complete.")
        else
            lm3:Log("ERROR: Start client LM failed.")
            obj.ClientList[ev].isConnected = false
        end
    end)
 
    -- отключение клиента
    -- остановка ЛМ
    obj:AddInEvent("DoDisconnect", "int", function(o,ev)
        lm3:Log("Client disconnected:"..tostring(ev))
        obj:OnStopClientLM(obj.ClientList[ev].LMName)
        obj.ClientList[ev].isConnected = false
    end)
 
    -- распределение данных от клиента
    obj:AddInEvent("DoIncoming", "table[Tag:int,Data:array[table[Object:string,Command:string,Name:string,[Data]:any]]]", function(o, ev)
        if obj.ClientList[ev.Tag].isConnected ~= true then
            lm3:Log("ERROR: Incoming data from not connected client: "..tostring(ev.Tag))
        else
            lm3:Log("Incoming data from client with tag: "..tostring(ev.Tag).." : "..lm3:DataToString(ev.Data))
            local data = ev.Data
            for i = 1,#data do
                local ObjName = data[i].Object
                if ObjName == "Auth" then
                    obj:DoCommand( { Tag = ev.Tag, Data = data[i] } )
                elseif ObjName == "Info" or ObjName == "Settings" then
                    obj:OnClientEvent({ LMName = obj.ClientList[ev.Tag].LMName, EventName = "OnExtEvent", Data = data[i] })
                else
                    lm3:Log("Warning: Incoming command to unknown object: "..lm3:DataToString(ObjName))
                end
            end
        end
    end)
 
    -- обработка данных от ЛМ
    obj:AddInEvent("DoClientEvent", "table[LMName:string,EventName:string,Data:any]", function(o, ev)
        local iClient = obj.TagFromLMName(ev.LMName)
        obj:OnReply( { Tag = iClient, Data = ev.Data } )
    end)
 
    -- обработка запросов клиента к основной ЛМ
    obj:AddInEvent("DoCommand", "table[Tag:int,Data:table[Object:string,Command:string,Name:string,Data:any]]", function(o, ev)
        if ev.Data.Command == "Set" then
            if ev.Data.Name == "UserName" then
                obj.ClientList[ev.Tag].UserName = ev.Data.Data
            elseif ev.Data.Name == "UserMail" then
                obj.ClientList[ev.Tag].UserMail = ev.Data.Data
                obj:OnReply( { Tag = ev.Tag, Data = { {Object = "Auth", Command = "Value", Name = "TaskObject", Data = "Task"} } } )
            end
        end
    end)
 
    -- отправь данные клиенту
    obj:AddOutEvent("OnReply", "table[Tag:int,Data:array[table[Object:string,Command:string,Name:string,[Data]:any]]]")
 
    -- отправить данные в ЛМ
    obj:AddOutProc("OnClientEvent", "table[LMName:string,EventName:string,Data:any]", "bool")
 
    obj:AddOutProc("OnStartClientLM", "table[Name:string]", "bool")
 
    obj:AddOutProc("OnStopClientLM", "string", "bool")
 
    return obj
end

main / client_lm / start.lua

lm3:LoadLibrary( {Alias = "sys", FileName = "lm3system"} )
lm3:Include("client_lib.lua")
 
-- объект Timer для alive сообщение
lm3:CreateObject( { Type = "Timer", Name = "timer" } )
timer:SetAttr("Interval", 30*1000)
 
-- объект Settings
-- позволяет менять конфигурацию ЛМ
init_Settings("Settings")
 
-- объект Info
-- возвращает информацию о курсах
init_Info("Info")
 
lm3:CreateLObject( { LibAlias = "sys", Type = "FileSystem", Name = "files" } )
 
lm3:CreateObject( { Type = "LuaData", Name = "lualoader" } )
 
lm3:AddHandler("OnStart", function(o, ev)
    Info:DoInitCourceList()
    timer:DoEnable(true)
end)
 
-- обработчик ивента с основной логической машины
-- распределяет запросы по клиентам
lm3:AddHandler("OnExtEvent", function(o, ev)
    local work_obj = lm3:GetObject(ev.Object)
    if work_obj ~= nil then
        if ev.Command == "Event" then
            lm3:Log("Fire client event:"..ev.Name.."("..lm3:DataToString(ev.Data)..")")
            work_obj:FireEvent(ev.Name, ev.Data)
        end
    else
        lm3:Log("ERROR: Incoming command to unknown object:"..lm3:DataToString(ev))
    end
end)
 
-- обработчик ответа от объекта
Info:AddHandler("OnCourceList", function(o, ev)
    lm3:DoExtEvent({ { Object = "Info", Command = "Value", Name = "CourceList", Data = ev } })
end)
 
timer:AddHandler("OnTick", function(o, ev)
    --lm3:Log("OnTick: send Alive")
    local data = {}
    data[1] = { Object = "Settings", Command = "Event", Name = "Alive" }
    lm3:DoExtEvent(data)
end)

Логи

[client]

2020-11-26 18:34:50.542 23236-23236/com.example.lm3_client D/lm3: application: onCreate
2020-11-26 18:34:50.559 23236-23236/com.example.lm3_client D/lm3: application: setNetworkStatus: 2
-- подключение к серверу
2020-11-26 18:34:50.559 23236-23236/com.example.lm3_client D/lm3: connect: startServerConnection
2020-11-26 18:34:50.560 23236-23285/com.example.lm3_client D/lm3: connect: start
2020-11-26 18:34:50.561 23236-23284/com.example.lm3_client D/lm3: application: setNetworkStatus: 2
2020-11-26 18:34:50.578 23236-23285/com.example.lm3_client D/lm3: connect: setServerConnection: true
2020-11-26 18:34:50.579 23236-23285/com.example.lm3_client D/lm3: connect: done
2020-11-26 18:34:50.579 23236-23285/com.example.lm3_client D/lm3: connect: end
2020-11-26 18:34:50.579 23236-23287/com.example.lm3_client D/lm3: connect: start listen
-- авторизация
2020-11-26 18:34:50.579 23236-23287/com.example.lm3_client D/lm3: auth: auth
2020-11-26 18:34:50.579 23236-23287/com.example.lm3_client D/lm3: auth: SetName: testtest
2020-11-26 18:34:50.579 23236-23287/com.example.lm3_client D/lm3: listen: serverSend
2020-11-26 18:34:50.580 23236-23287/com.example.lm3_client D/lm3: auth: SetMail: test@test.test
2020-11-26 18:34:50.580 23236-23287/com.example.lm3_client D/lm3: listen: serverSend
-- установка локализации
2020-11-26 18:34:50.581 23236-23287/com.example.lm3_client D/lm3: settings: SetLanguage: EN
2020-11-26 18:34:50.581 23236-23287/com.example.lm3_client D/lm3: listen: serverSend
2020-11-26 18:34:50.589 23236-23236/com.example.lm3_client D/lm3: activity: main: OnCreate
2020-11-26 18:34:50.643 23236-23287/com.example.lm3_client D/lm3: auth: setTaskObject
2020-11-26 18:34:50.643 23236-23287/com.example.lm3_client D/lm3: info: GetCourseList
2020-11-26 18:34:50.643 23236-23287/com.example.lm3_client D/lm3: listen: serverSend
2020-11-26 18:34:50.697 23236-23236/com.example.lm3_client D/lm3: activity: main: onResume
-- запрос списка курсов
2020-11-26 18:34:50.886 23236-23287/com.example.lm3_client D/lm3: info: setCourseList: 
2020-11-26 18:34:50.887 23236-23287/com.example.lm3_client D/lm3: info: Introduction to Somnology
2020-11-26 18:34:50.887 23236-23287/com.example.lm3_client D/lm3: info: Snoring problems
2020-11-26 18:35:07.609 23236-23236/com.example.lm3_client D/lm3: activity: main: onPause
2020-11-26 18:35:07.624 23236-23236/com.example.lm3_client D/lm3: activity: settings: OnCreate
2020-11-26 18:35:07.674 23236-23236/com.example.lm3_client D/lm3: activity: settings: OnCreate: current locale: en
2020-11-26 18:35:07.674 23236-23236/com.example.lm3_client D/lm3: activity: settings: OnCreate: locale in memory: EN
2020-11-26 18:35:07.682 23236-23236/com.example.lm3_client D/lm3: activity: settings: onResume
-- установка локализации
2020-11-26 18:35:09.775 23236-23236/com.example.lm3_client D/lm3: activity: settings: changeLocale: start: RU
2020-11-26 18:35:09.775 23236-23236/com.example.lm3_client D/lm3: activity: settings: changeLocale: change from: EN
2020-11-26 18:35:09.776 23236-23236/com.example.lm3_client D/lm3: settings: SetLanguage: RU
2020-11-26 18:35:09.777 23236-23236/com.example.lm3_client D/lm3: listen: serverSend
-- запрос списка курсов
2020-11-26 18:35:09.779 23236-23236/com.example.lm3_client D/lm3: info: GetCourseList
2020-11-26 18:35:09.779 23236-23236/com.example.lm3_client D/lm3: listen: serverSend
2020-11-26 18:35:09.786 23236-23236/com.example.lm3_client D/lm3: activity: settings: changeLocale: check: ru
2020-11-26 18:35:09.822 23236-23236/com.example.lm3_client D/lm3: activity: settings: OnCreate
2020-11-26 18:35:09.828 23236-23287/com.example.lm3_client D/lm3: info: setCourseList: 
2020-11-26 18:35:09.828 23236-23287/com.example.lm3_client D/lm3: info: Введение в сомнологию
2020-11-26 18:35:09.828 23236-23287/com.example.lm3_client D/lm3: info: Проблемы храпа
2020-11-26 18:35:09.872 23236-23236/com.example.lm3_client D/lm3: activity: settings: OnCreate: current locale: ru
2020-11-26 18:35:09.872 23236-23236/com.example.lm3_client D/lm3: activity: settings: OnCreate: locale in memory: RU
2020-11-26 18:35:09.877 23236-23236/com.example.lm3_client D/lm3: activity: settings: onResume

[server] LM_main.log

[2020-11-27 11:39:50][INFO:LMInit][Start logic machine v3.3(Build:555) ----------------------------------]
[2020-11-27 11:39:50][LUA:LMCore][Starting server]
[2020-11-27 11:39:50][TRACE:LMInit][Create library object: server]
[2020-11-27 11:39:50][TRACE:LMInit][Create library object: packer]
[2020-11-27 11:39:50][TRACE:LMInit][Create object: clients]
[2020-11-27 11:39:50][TRACE:LMInit][Create object: Auth]
[2020-11-27 11:39:50][INFO:LMInit][Initialization complete.]
[2020-11-27 11:39:51][LUA:LMCore][New client connected:1]
[2020-11-27 11:39:51][LUA:LMCore][Start client LM...]
[2020-11-27 11:39:51][LUA:LMCore][Start client LM complete.]
-- авторизация клиента
[2020-11-27 11:39:51][LUA:LMCore][Incoming data from client with tag: 1 : [Array:1]
  1 = [Table:4]
    "Command" = [String] "Set"
    "Data" = [String] "testtest"
    "Name" = [String] "UserName"
    "Object" = [String] "Auth"]
[2020-11-27 11:39:51][LUA:LMCore][Incoming data from client with tag: 1 : [Array:1]
  1 = [Table:4]
    "Command" = [String] "Set"
    "Data" = [String] "test@test.test"
    "Name" = [String] "UserMail"
    "Object" = [String] "Auth"]
[2020-11-27 11:39:51][LUA:LMCore][Auth: OnReply: tag: 1 : [Array:1]
  1 = [Table:4]
    "Command" = [String] "Value"
    "Data" = [String] "Task"
    "Name" = [String] "TaskObject"
    "Object" = [String] "Auth"]
[2020-11-27 11:39:51][LUA:LMCore][Incoming data from client with tag: 1 : [Array:1]
  1 = [Table:4]
    "Command" = [String] "Event"
    "Data" = [String] "RU"
    "Name" = [String] "DoChangeLang"
    "Object" = [String] "Settings"]
-- запрос списка курсов
[2020-11-27 11:39:51][LUA:LMCore][Incoming data from client with tag: 1 : [Array:1]
  1 = [Table:3]
    "Command" = [String] "Event"
    "Name" = [String] "CourceList"
    "Object" = [String] "Info"]
[2020-11-27 11:39:51][LUA:LMCore][Auth: OnReply: tag: 1 : [Array:1]
  1 = [Table:4]
    "Command" = [String] "Value"
    "Data" = [Array:2]
      1 = [Table:3]
        "CourceID" = [String] "c1"
        "Description" = [String] "Хотите быстро засыпать, высыпаться и чувствовать себя отлично в течение дня? Узнайте все о том, как наладить правильный сон."
        "Name" = [String] "Введение в сомнологию"
      2 = [Table:3]
        "CourceID" = [String] "c2"
        "Description" = [String] "N/A"
        "Name" = [String] "Проблемы храпа"
    "Name" = [String] "CourceList"
    "Object" = [String] "Info"]
-- смена локализации
[2020-11-27 11:40:15][LUA:LMCore][Incoming data from client with tag: 1 : [Array:1]
  1 = [Table:4]
    "Command" = [String] "Event"
    "Data" = [String] "EN"
    "Name" = [String] "DoChangeLang"
    "Object" = [String] "Settings"]
-- запрос списка курсов
[2020-11-27 11:40:15][LUA:LMCore][Incoming data from client with tag: 1 : [Array:1]
  1 = [Table:3]
    "Command" = [String] "Event"
    "Name" = [String] "CourceList"
    "Object" = [String] "Info"]
[2020-11-27 11:40:15][LUA:LMCore][Auth: OnReply: tag: 1 : [Array:1]
  1 = [Table:4]
    "Command" = [String] "Value"
    "Data" = [Array:2]
      1 = [Table:3]
        "CourceID" = [String] "c1"
        "Description" = [String] "Want to fall asleep fast, get enough sleep and feel great throughout the day? Learn all about how to get good sleep."
        "Name" = [String] "Introduction to Somnology"
      2 = [Table:3]
        "CourceID" = [String] "c2"
        "Description" = [String] "N/A"
        "Name" = [String] "Snoring problems"
    "Name" = [String] "CourceList"
    "Object" = [String] "Info"]

[client] LM_clm_1.log

[2020-11-27 11:39:51][INFO:LMInit][Start logic machine v3.3(Build:555) ----------------------------------]
[2020-11-27 11:39:51][TRACE:LMInit][Create object: timer]
[2020-11-27 11:39:51][TRACE:LMInit][Create object: Settings]
[2020-11-27 11:39:51][TRACE:LMInit][Create object: Info]
[2020-11-27 11:39:51][TRACE:LMInit][Create library object: files]
[2020-11-27 11:39:51][TRACE:LMInit][Create object: lualoader]
[2020-11-27 11:39:51][INFO:LMInit][Initialization complete.]
-- авторизация клиента
[2020-11-27 11:39:51][LUA:LMCore][Fire client event:DoChangeLang([String] "RU")]
-- запрос списка курсов
[2020-11-27 11:39:51][LUA:LMCore][Fire client event:CourceList([Nil])]
-- смена локализации клиентом
[2020-11-27 11:40:15][LUA:LMCore][Fire client event:DoChangeLang([String] "EN")]
[2020-11-27 11:40:15][LUA:LMCore][Settings Event: DoChangeLang: Change to: EN]
-- запрос списка курсов
[2020-11-27 11:40:15][LUA:LMCore][Fire client event:CourceList([Nil])]
lm3/ce/t_multi_lang.txt · Последнее изменение: 2020/11/30 15:06 — ruben