343 lines
14 KiB
Lua
343 lines
14 KiB
Lua
-- -------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- RAILUALIB TRANSLATION MODULE
|
|
-- Requests and organizes translations for localised strings.
|
|
|
|
-- dependencies
|
|
local event = require("__RaiLuaLib__.lualib.event")
|
|
local migration = require("__RaiLuaLib__.lualib.migration")
|
|
|
|
-- locals
|
|
local math_floor = math.floor
|
|
local string_gsub = string.gsub
|
|
local string_lower = string.lower
|
|
local table_sort = table.sort
|
|
|
|
-- object
|
|
local translation = {}
|
|
|
|
-- internal events
|
|
translation.start_event = event.get_id("translation_start")
|
|
translation.finish_event = event.get_id("translation_finish")
|
|
translation.canceled_event = event.get_id("translation_canceled")
|
|
|
|
-- converts a localised string into a format readable by the API
|
|
-- basically just spits out the table in string form
|
|
local function serialise_localised_string(t)
|
|
local output = "{"
|
|
if type(t) == "string" then return t end
|
|
for _,v in pairs(t) do
|
|
if type(v) == "table" then
|
|
output = output..serialise_localised_string(v)
|
|
else
|
|
output = output.."\""..v.."\", "
|
|
end
|
|
end
|
|
output = string_gsub(output, ", $", "").."}"
|
|
return output
|
|
end
|
|
|
|
-- translate 50 entries per tick
|
|
local function translate_batch(e)
|
|
local __translation = global.__lualib.translation
|
|
local iterations = math_floor(50 / __translation.active_translations_count)
|
|
if iterations < 1 then iterations = 1 end
|
|
local players = __translation.players
|
|
-- for each player that is doing a translation
|
|
for _,pi in ipairs(e.registered_players) do
|
|
local pt = players[pi]
|
|
local request_translation = game.get_player(pi).request_translation
|
|
local next_index = pt.next_index
|
|
local finish_index = next_index + iterations
|
|
local strings = pt.strings
|
|
local strings_len = pt.strings_len
|
|
-- request translations for the next n strings
|
|
for i=next_index,finish_index do
|
|
if i > strings_len then
|
|
-- deregister this event for this player
|
|
event.disable("translation_translate_batch", pi)
|
|
goto continue
|
|
end
|
|
request_translation(strings[i])
|
|
end
|
|
-- update next index
|
|
pt.next_index = finish_index + 1
|
|
::continue::
|
|
end
|
|
end
|
|
|
|
-- sorts a translated string into its appropriate dictionaries
|
|
local function sort_translated_string(e)
|
|
local __translation = global.__lualib.translation
|
|
local player_data = __translation.players[e.player_index]
|
|
local active_translations = player_data.active_translations
|
|
local localised = e.localised_string
|
|
local serialised = serialise_localised_string(localised)
|
|
-- check if the string actually exists in the registry. if it does not, then another mod requested this translation as well and it was already sorted.
|
|
local string_registry = player_data.string_registry[serialised]
|
|
if string_registry then
|
|
-- for each dictionary that requested this string
|
|
for dictionary_name, internal_names in pairs(string_registry) do
|
|
local data = active_translations[dictionary_name]
|
|
-- extra sanity check
|
|
if data then
|
|
-- remove from registry index
|
|
data.registry_index[serialised] = nil
|
|
data.registry_index_size = data.registry_index_size - 1
|
|
|
|
-- check if the string was successfully translated
|
|
local success = e.translated
|
|
local result = e.result
|
|
local include_failed_translations = data.include_failed_translations
|
|
if not include_failed_translations and (not success or result == "") then
|
|
log(dictionary_name..": key "..serialised.." was not successfully translated, and will not be included in the output.")
|
|
else
|
|
-- do this only if the result will be the same for all internal names
|
|
if success then
|
|
-- add to lookup table
|
|
data.lookup[string_lower(result)] = internal_names
|
|
-- add to sorted results table
|
|
data.sorted_translations[#data.sorted_translations+1] = data.lowercase_sorted_translations and string_lower(result) or result
|
|
end
|
|
|
|
-- for every internal name that this string applies do
|
|
for i=1,#internal_names do
|
|
local internal = internal_names[i]
|
|
-- set result to internal name if the translation failed and the option is active
|
|
if not success and include_failed_translations then
|
|
result = internal
|
|
-- add to lookup and sorted_translations tables here, as each iteration will have a different name
|
|
local lookup = data.lookup[result]
|
|
if lookup then
|
|
lookup[#lookup+1] = internal
|
|
else
|
|
data.lookup[result] = {internal}
|
|
end
|
|
data.sorted_translations[#data.sorted_translations+1] = result
|
|
end
|
|
-- add to translations table
|
|
if data.translations[internal] then
|
|
error("Duplicate key ["..internal.."] in dictionary: "..dictionary_name)
|
|
else
|
|
data.translations[internal] = result
|
|
end
|
|
end
|
|
end
|
|
|
|
-- check if this dictionary has finished translation
|
|
if data.registry_index_size == 0 then
|
|
-- sort sorted results table
|
|
table_sort(data.sorted_translations)
|
|
-- decrement active translation counters
|
|
__translation.active_translations_count = __translation.active_translations_count - 1
|
|
player_data.active_translations_count = player_data.active_translations_count - 1
|
|
-- raise finished event with the output tables
|
|
event.raise(translation.finish_event, {player_index=e.player_index, dictionary_name=dictionary_name, lookup=data.lookup,
|
|
sorted_translations=data.sorted_translations, translations=data.translations})
|
|
-- remove from active translations table
|
|
player_data.active_translations[dictionary_name] = nil
|
|
|
|
-- check if the player is done translating
|
|
if player_data.active_translations_count == 0 then
|
|
-- deregister events from this player
|
|
event.disable("translation_translate_batch", e.player_index)
|
|
event.disable("translation_sort_result", e.player_index)
|
|
-- remove player's translation table
|
|
__translation.players[e.player_index] = nil
|
|
end
|
|
end
|
|
else
|
|
error("Data for dictionary: "..dictionary_name.." for player: "..e.player_index.." does not exist!")
|
|
end
|
|
end
|
|
|
|
-- remove from string registry
|
|
player_data.string_registry[serialised] = nil
|
|
end
|
|
end
|
|
|
|
translation.serialise_localised_string = serialise_localised_string
|
|
|
|
-- begin translating strings
|
|
function translation.start(player, dictionary_name, data, options)
|
|
options = options or {}
|
|
local __translation = global.__lualib.translation
|
|
local player_data = __translation.players[player.index]
|
|
|
|
-- create player table if it doesn't exist
|
|
if not player_data then
|
|
__translation.players[player.index] = {
|
|
active_translations = {}, -- contains data for each dictionary that is being translated
|
|
active_translations_count = 0, -- count of translations that this player is performing
|
|
next_index = 1, -- index of the next string to be translated
|
|
string_registry = {}, -- contains data on where a translation should be placed
|
|
strings = {}, -- contains the actual localised string objects to be translated
|
|
strings_len = 0 -- length of the strings table, for use in on_tick to avoid extraneous logic
|
|
}
|
|
player_data = __translation.players[player.index]
|
|
-- reset if the translation is already running
|
|
elseif player_data.active_translations[dictionary_name] then
|
|
log("Cancelling and restarting translation of "..dictionary_name.." for "..player.name)
|
|
translation.cancel(player, dictionary_name)
|
|
end
|
|
|
|
-- create local references
|
|
local string_registry = player_data.string_registry
|
|
local strings = player_data.strings
|
|
|
|
local registry_index = {} -- contains a table of keys that represent all the places in the string index that this dictionary has a place in
|
|
|
|
-- add data to translation tables
|
|
for i=1,#data do
|
|
local t = data[i]
|
|
local localised = t.localised
|
|
local serialised = serialise_localised_string(localised)
|
|
-- check for this string in the global string registry
|
|
local registry_entry = string_registry[serialised]
|
|
if registry_entry then
|
|
-- check if this dictionary has been added to this registry yet
|
|
if registry_index[serialised] then
|
|
local our_registry = registry_entry[dictionary_name]
|
|
our_registry[#our_registry+1] = t.internal
|
|
else
|
|
registry_index[serialised] = true
|
|
registry_entry[dictionary_name] = {t.internal}
|
|
end
|
|
else
|
|
-- this is a new string, so add it to the strings table and create the registry
|
|
strings[#strings+1] = localised
|
|
string_registry[serialised] = {[dictionary_name]={t.internal}}
|
|
registry_index[serialised] = true
|
|
end
|
|
end
|
|
|
|
-- set new strings table length
|
|
player_data.strings_len = #strings
|
|
|
|
-- add this dictionary"s data to the player"s table
|
|
player_data.active_translations[dictionary_name] = {
|
|
-- string registry index
|
|
registry_index = registry_index,
|
|
registry_index_size = table_size(registry_index), -- used to determine when the translation has finished
|
|
-- options
|
|
lowercase_sorted_translations = options.lowercase_sorted_translations,
|
|
include_failed_translations = options.include_failed_translations,
|
|
-- output
|
|
lookup = {},
|
|
sorted_translations = {},
|
|
translations = {}
|
|
}
|
|
|
|
-- increment active translations counters, register on_tick and sort result handlers
|
|
__translation.active_translations_count = __translation.active_translations_count + 1
|
|
player_data.active_translations_count = player_data.active_translations_count + 1
|
|
-- raise translation start event
|
|
event.raise(translation.start_event, {player_index=player.index, dictionary_name=dictionary_name})
|
|
-- register events, if needed
|
|
event.enable("translation_translate_batch", player.index)
|
|
event.enable("translation_sort_result", player.index)
|
|
end
|
|
|
|
-- cancel a translation
|
|
function translation.cancel(player, dictionary_name)
|
|
local __translation = global.__lualib.translation
|
|
local player_data = __translation.players[player.index]
|
|
local translation_data = player_data.active_translations[dictionary_name]
|
|
if not translation_data then
|
|
log("Tried to cancel a translation that isn't running!")
|
|
return
|
|
end
|
|
log("Canceling translation of ["..dictionary_name.."] for player ["..player.name.."]")
|
|
|
|
-- remove this dictionary from the string registry
|
|
local string_registry = player_data.string_registry
|
|
for key,_ in pairs(translation_data.registry_index) do
|
|
local key_registry = string_registry[key]
|
|
key_registry[dictionary_name] = nil
|
|
if table_size(key_registry) == 0 then
|
|
string_registry[key] = nil
|
|
end
|
|
end
|
|
|
|
-- decrement active translation counters
|
|
__translation.active_translations_count = __translation.active_translations_count - 1
|
|
player_data.active_translations_count = player_data.active_translations_count - 1
|
|
-- raise canceled event with the output tables
|
|
event.raise(translation.canceled_event, {player_index=player.index, dictionary_name=dictionary_name})
|
|
-- remove from active translations table
|
|
player_data.active_translations[dictionary_name] = nil
|
|
|
|
-- check if the player is done translating
|
|
if player_data.active_translations_count == 0 then
|
|
-- deregister events for this player
|
|
event.disable("translation_sort_result", player.index)
|
|
-- only deregister this if it's actually registered
|
|
if event.is_enabled("translation_translate_batch", player.index) then
|
|
event.disable("translation_translate_batch", player.index)
|
|
end
|
|
-- remove player's translation table
|
|
__translation.players[player.index] = nil
|
|
end
|
|
end
|
|
|
|
-- cancels all translations for a player
|
|
function translation.cancel_all_for_player(player)
|
|
local __translation = global.__lualib.translation
|
|
local player_translations = __translation.players[player.index].active_translations
|
|
for name,_ in pairs(player_translations) do
|
|
translation.cancel(player, name)
|
|
end
|
|
end
|
|
|
|
-- cancels ALL translations for this mod
|
|
function translation.cancel_all()
|
|
for i,t in pairs(global.__lualib.translation.players) do
|
|
local player = game.get_player(i)
|
|
for name,_ in pairs(t.active_translations) do
|
|
translation.cancel(player, name)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- register conditional events
|
|
event.register_conditional{
|
|
translation_translate_batch = {id=defines.events.on_tick, handler=translate_batch, options={skip_validation=true, suppress_logging=true}},
|
|
translation_sort_result = {id=defines.events.on_string_translated, handler=sort_translated_string, options={skip_validation=true, suppress_logging=true}},
|
|
}
|
|
|
|
-- set up global
|
|
event.on_init(function()
|
|
-- this requires the event module so the lualib table will already exist
|
|
global.__lualib.translation = {
|
|
active_translations_count = 0,
|
|
players = {}
|
|
}
|
|
translation.retranslate_all_event = remote.call("railualib_translation", "retranslate_all_event")
|
|
end)
|
|
|
|
event.on_load(function()
|
|
translation.retranslate_all_event = remote.call("railualib_translation", "retranslate_all_event")
|
|
end)
|
|
|
|
event.on_configuration_changed(function()
|
|
migration.run(global.__lualib.__version, {
|
|
["0.2.6"] = function()
|
|
-- remove unneeded translation tables
|
|
local players = global.__lualib.translation.players
|
|
for i,t in pairs(players) do
|
|
if t.active_translations_count == 0 then
|
|
players[i] = nil
|
|
end
|
|
end
|
|
end
|
|
})
|
|
end)
|
|
|
|
-- cancel all translations for the player when they leave or are removed
|
|
event.register({defines.events.on_pre_player_left_game, defines.events.on_pre_player_removed}, function(e)
|
|
local player_translation = global.__lualib.translation.players[e.player_index]
|
|
if player_translation and player_translation.active_translations_count > 0 then
|
|
translation.cancel_all_for_player(game.get_player(e.player_index))
|
|
end
|
|
end)
|
|
|
|
return translation |