Page 1 of 1

How to get the translation?

Posted: Mon May 09, 2022 12:10 am
by yaim904
What am I doing wrong?
I am trying to get the translation of the texts I want.

Code: Select all

-- Control.lue
local localised_name = { "recipe-name.iron-plate" }
Player.request_translation( localised_name )
The event is not receiving anything

Code: Select all

script.on_event( defines.events.on_string_translated, function( event )
    log( "LoadTranslation" )
end)
What am I doing wrong?

Re: How to get the translation?

Posted: Mon May 09, 2022 3:15 am
by Pi-C
The event will be triggered only if the string could be translated, i.e. if the return value of LuaPlayer::request_translation(localised_string) is true.

Re: How to get the translation?

Posted: Mon May 09, 2022 11:09 am
by yaim904
Pi-C wrote:
Mon May 09, 2022 3:15 am
The event will be triggered only if the string could be translated, i.e. if the return value of LuaPlayer::request_translation(localised_string) is true.
I don't understand you, because the function always returns true

But just in case, I tried other variants and it still doesn't work.

Code: Select all

-- Control.lue
local localised_name = { "item-name.iron-plate" }
Player.request_translation( localised_name )

local localised_name = { "recipe-name.iron-plate" }
Player.request_translation( localised_name )

local localised_name = { "entity-name.iron-plate" }
Player.request_translation( localised_name )
I never see this message in "factorio-current.log"

Code: Select all

script.on_event( defines.events.on_string_translated, function( event )
    log( "LoadTranslation" )
end)
What am I doing wrong?

Re: How to get the translation?

Posted: Tue May 10, 2022 12:14 am
by DaveMcW
Paste this in the console.

Code: Select all

/c
script.on_event(defines.events.on_string_translated, function(event)
  game.print(event.result)
end)
game.player.request_translation({"item-name.iron-plate"})

Re: How to get the translation?

Posted: Tue May 10, 2022 1:18 pm
by yaim904
Reading your comments it occurred to me to try some things
Pi-C wrote:
Mon May 09, 2022 3:15 am
DaveMcW wrote:
Tue May 10, 2022 12:14 am
This code worked!!

Code: Select all

-- Control.lua
local localised_name = Player.forge.recipes[ "iron-plate" ]
localised_name = localised_name.localised_name
Player.request_translation( localised_name )

script.on_event(
    defines.events.on_string_translated,
    function( event )
        log( "LoadTranslation" )
    end
)
Although, it only works when creating a map and not when loading a save game.

The events I am using are:

Code: Select all

on_init( )
defines.events.on_player_created
defines.events.on_player_changed_force
Check the Data lifecycle and it is following the sequence [ 1 ] Control.lua, [ 2 ] on_init( ) and [ 3 ] Migrations
Image

For some reason the event is not executed when the game loads.
Do you know of any way to activate the event when a game loads?

Re: How to get the translation?

Posted: Tue May 10, 2022 1:28 pm
by Pi-C
yaim904 wrote:
Mon May 09, 2022 11:09 am
Pi-C wrote:
Mon May 09, 2022 3:15 am
The event will be triggered only if the string could be translated, i.e. if the return value of LuaPlayer::request_translation(localised_string) is true.
I don't understand you, because the function always returns true
Here it says:
request_translation(localised_string) → boolean

Requests a translation for the given localised string. If the request is successful the on_string_translated event will be fired at a later time with the results.
So there must be cases where the function will return false. That would have explained why the event wasn't triggered.

Re: How to get the translation?

Posted: Tue May 10, 2022 3:41 pm
by robot256
Do you know of any way to activate the event when a game loads?
Normally that would be the purpose of on_load(), in the lower branch of the data lifestyle diagram. It runs when you load the save, and is only allowed to do things to restore the game state to exactly what it was before you saved and quit. I think it is not allowed to issue new event requests (either it will cause an immediate error, or it will cause a desync in multiplayer).

The correct option in your case would be to store the results you get from the first event in the global table, so that you can access it whenever you want. This is stored in the save file, so it will already be present when you load a game. When later player events fire, you can uodate the values in the global table.

Re: How to get the translation?

Posted: Tue May 10, 2022 4:36 pm
by yaim904
robot256 wrote:
Tue May 10, 2022 3:41 pm
I wanted to avoid that option because of the weight (in MB) that would mean.
But if I have no other choice...

If I do it as you say, when should I upload the translations?
That is, in such a way that it affects the players who open a previously saved game.
I want to load all the information before giving control of the character to the player.

Re: How to get the translation?

Posted: Tue May 10, 2022 5:12 pm
by robot256
I'm not very familiar with how the translation API works. Does it do things that you can't with simple localised printing? Where do you need to display the translated strings?

Normally I just do, for example, "game.print({"item-name.iron-plate"})" and it selects the correct translation for each player that seea it.

Re: How to get the translation?

Posted: Tue May 10, 2022 6:01 pm
by Xorimuth
robot256 wrote:
Tue May 10, 2022 5:12 pm
I'm not very familiar with how the translation API works. Does it do things that you can't with simple localised printing? Where do you need to display the translated strings?

Normally I just do, for example, "game.print({"item-name.iron-plate"})" and it selects the correct translation for each player that seea it.
The only use I've seen is it allows you to do your own searching for items by name, as used in Recipe Book: https://mods.factorio.com/mod/RecipeBook. Pretty much any other use should just be printed/displayed as a localised string, yes.

Re: How to get the translation?

Posted: Tue May 10, 2022 6:14 pm
by yaim904
robot256 wrote:
Tue May 10, 2022 5:12 pm
You're right
I haven't said why I'm doing all this.

I want to create a dictionary from which to filter a word (or fragment of a word) in the translations.
Xorimuth wrote:
Tue May 10, 2022 6:01 pm
Yes, that's what I'm looking for, but that MOD uses a library that I don't understand, and I have tried to understand it.

Re: How to get the translation?

Posted: Tue May 10, 2022 6:36 pm
by Xorimuth
yaim904 wrote:
Tue May 10, 2022 6:14 pm
robot256 wrote:
Tue May 10, 2022 5:12 pm
You're right
I haven't said why I'm doing all this.

I want to create a dictionary from which to filter a word (or fragment of a word) in the translations.
Xorimuth wrote:
Tue May 10, 2022 6:01 pm
Yes, that's what I'm looking for, but that MOD uses a library that I don't understand, and I have tried to understand it.
Fair enough - though I'd say that a lot of work and thought has been put into that library (https://github.com/factoriolib/flib/blo ... ionary.lua, https://factoriolib.github.io/flib/modu ... onary.html) so you'd save yourself a lot of time and effort by using it. Have you seen the example: https://factoriolib.github.io/flib/exam ... y.lua.html# ?

Re: How to get the translation?

Posted: Tue May 10, 2022 7:37 pm
by robot256
Your use case makes sense to me. No, there is no way to avoid keeping the dictionary in global if you want to avoid recreating it from scratch every time the user enters a query. It is really not that much memory, only a few kilobytes of text per player, and it will compress well in the save file zip. I second Xorimuth's encouragement to use the Flib module, because it does exactly what you need already.

Re: How to get the translation?

Posted: Tue May 10, 2022 8:08 pm
by Xorimuth
Note that you can use flib's set_use_local_storage to avoid storing anything in global (I think) but at the expense of needing to regenerate the dictionary every load (generating the dictionary causes quite large network traffic in multiplayer).

Re: How to get the translation?

Posted: Wed May 11, 2022 3:54 pm
by yaim904
Xorimuth wrote:
Tue May 10, 2022 6:36 pm
Fair enough - though I'd say that a lot of work and thought has been put into that library (https://github.com/factoriolib/flib/blo ... ionary.lua, https://factoriolib.github.io/flib/modu ... onary.html) so you'd save yourself a lot of time and effort by using it. Have you seen the example: https://factoriolib.github.io/flib/exam ... y.lua.html# ?
I did a quick test with the dictionary example, but they have the same problem (doesn't work on pre-saves without the mod).

Xorimuth wrote:
Tue May 10, 2022 6:01 pm
The only use I've seen is it allows you to do your own searching for items by name, as used in Recipe Book: https://mods.factorio.com/mod/RecipeBook. Pretty much any other use should just be printed/displayed as a localised string, yes.
I checked the code RecipeBook and i found that this mod does work, but it does it with the on_tick.

Taking into account the above and taking advantage of the fact that my MOD does not need to start with the game or the player, I am thinking of making the translations load when the player connects to the game for the first time (Player.online_time >= X) and save all the information in a local variable.

I welcome comments, suggestions and advice.

Re: How to get the translation?

Posted: Wed May 11, 2022 4:42 pm
by robot256
Is on_player_joined_game fired when loading a single player map? I think there is a small bug in the example. This code from on_configuration_changed should also be at the end of on_init to handle when the mod is added to an existing single player map.

Code: Select all

-- Request translations for all connected players
    for _, player in pairs(game.players) do
      if player.connected then
        dictionary.translate(player)
      end
    end
Player.online_time only tells you how long a player has been connected total since the game started. It won't tell you if they are connected right now or how long it has been since they joined most recently.

Re: How to get the translation?

Posted: Wed May 11, 2022 5:09 pm
by Pi-C
robot256 wrote:
Wed May 11, 2022 4:42 pm
Is on_player_joined_game fired when loading a single player map?
IIRC, the event fires only once in single player, after on_player_created. So the safe way to handle both single- and multiplayer games is to store player data in the global table when on_player_joined_game fires, and to reinitialize the data when on_configuration_changed triggers.

Re: How to get the translation?

Posted: Wed May 11, 2022 5:23 pm
by robot256
So if the mod is installed before the player joins, the mod can add the player's translation in on_player_joined_game. If the mod is installed after the player joined, it will add the player's translation in on_configuration_changed (which will run after on_init when adding the mod to an existing save).

What did you mean by
yaim904 wrote:doesn't work on pre-saves without the mod
?

Re: How to get the translation?

Posted: Wed May 11, 2022 8:09 pm
by yaim904
I will start by saying: I have not tried what you are saying.
robot256 wrote:
Wed May 11, 2022 5:23 pm
So if the mod is installed before the player joins, the mod can add the player's translation in on_player_joined_game. If the mod is installed after the player joined, it will add the player's translation in on_configuration_changed (which will run after on_init when adding the mod to an existing save).

What did you mean by
yaim904 wrote:doesn't work on pre-saves without the mod
?
The translation is loaded in the on_string_translated event, so I need the event to fire, which is not happening when loading a previously saved game (I'm testing in a single game).

I'll use on_tick to activate on_string_translated and load the translation no matter if the game is loading or being created (I'll code it over the weekend), I still have to perfect this idea, but it seems for the best.

I know, all this is done by Factorio Library, but I want to learn how to do it, so I can fix it when something goes wrong (or a game update makes the mod obsolete). Maybe later in a next version I will integrate it.

Re: How to get the translation?

Posted: Thu May 12, 2022 9:03 pm
by yaim904
I haven't tried this code yet, but it's an idea of what I want to do.

Code: Select all

-- Variable initialization
_G.OnTick = _G.OnTick or { }

-- Requests per tick
local TickRequest = 25

local function Add( Table, Data )
    if not Table[ 1 ] then table.insert( Table, { } ) end
    if #Table[ 1 ] > TickRequest - 1 then table.insert( Table, 1, { } ) end
    table.insert( Table[ 1 ], Data )
end

local function AddLocalised( Data )
    local Received = game.table_to_json( Data.Localised )
    local Expected = game.table_to_json( { "locale-identifier" } )

    OnTick.Queue = OnTick.Queue or { }
    local Queue = OnTick.Queue

    if Received == Expected then
        Queue.Language = Queue.Language or { }
        Add( Queue.Language, Data )
    else Add( Queue, Data ) end
end

script.on_event(
    defines.events.defines.events.on_tick,
    function ( Event )
       if not OnTick.Queue then return end
       local Queue = OnTick.Queue

       OnTick.Translated = OnTick.Translated or { }
       local Translated = OnTick.Translated

        if not Queue.Language then goto JumpLanguage end
        local Language = table.remove( Queue.Language )
        if #Queue.Language < 1 then Queue.Language = nil end
        for _, Data in pairs( Language ) do
            Data.Player.request_translation( Data.Localised )
        end
        Translated.Language = Translated.Language or { }
        table.insert( Translated.Language, Language )
        :: JumpLanguage ::

        if not Queue then goto JumpLocalised end
        local ArrayData = table.remove( Queue )
        if #Queue < 1 then OnTick.Queue = nil end
        for _, Data in pairs( ArrayData ) do
            Data.Player.request_translation( Data.Localised )
        end
        table.insert( Translated, ArrayData )
        :: JumpLocalised ::
    end
)

local function Remove( Data )
    for i = 1, #Data.Array, 1 do
        for j = 1, #Data.Array[ i ], 1 do
            local Element = Data.Array[ i ][ j ]
            Expected = game.table_to_json( Element.Localised )
            if Element.Player == Data.Player and Expected == Data.Received then
                -- Save data
                table.remove( Data.Array[ i ], j )
                if #Data.Array[ i ] < 1 then table.remove( Data.Array, i ) end
                if #Data.Array < 1 then Translated = nil end
                return true
            end
        end
    end
    return false
end

script.on_event(
    defines.events.defines.events.on_string_translated,
    function ( Event )
        if not OnTick.Translated then return end
        local Expected = game.table_to_json( { "locale-identifier" } )
        local Translated = OnTick.Translated

        local Data = { }
        Data.Player = game.get_player( Event.player_index )
        Data.Received = game.table_to_json( Event.localised_string )

        if Data.Received ~= Expected then goto JumpLanguage end
        if not Translated.Language then goto JumpLanguage end
        Data.Array = Translated.Language
        if Remove( Data ) then return end
        :: JumpLanguage ::

        if not Translated then goto JumpLocalised end
        Data.Array = Translated
        if Remove( Data ) then return end
        :: JumpLocalised ::
    end
)

script.on_init( function ( )
    AddLocalised( {
        [ 'Player' ] = { },
        [ 'MOD' ] = "MOD Name",
        [ 'Localised' ] = { '', { 'item-name.iron-plate' } }
    } )
    AddLocalised( {
        [ 'Player' ] = { },
        [ 'MOD' ] = "MOD Name",
        [ 'Localised' ] = { { 'locale-identifier' } }
    } )
end )
I've already checked out the Factorio Library code and they do it more efficiently using advanced string handling.

Which left me with several questions:
  • How much does it cost to make as many requests as I am doing?
  • When functions or variables are created globally, is this shared with another MOD??
As always, I will accept advice, recommendations, suggestions and comments.