425 lines
14 KiB
Lua
425 lines
14 KiB
Lua
local gui = require("__flib__/gui-lite")
|
|
local mod_gui = require("__core__/lualib/mod-gui")
|
|
local table = require("__flib__/table")
|
|
|
|
--- @diagnostic disable
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
local flib_dictionary = {}
|
|
|
|
local inner_separator = "" -- U+E000
|
|
local separator = "" -- U+E001
|
|
local translation_timeout = 600
|
|
|
|
-- Depending on the value of `use_local_storage`, this will be tied to `global` or will be re-generated during `on_load`
|
|
local raw = { _total_strings = 0 }
|
|
|
|
local use_local_storage = false
|
|
|
|
local function key_value(key, value)
|
|
return key .. inner_separator .. value .. separator
|
|
end
|
|
|
|
local RawDictionary = {}
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function RawDictionary:add(internal, translation)
|
|
local to_add = { "", internal, inner_separator, { "?", translation, "FLIB_TRANSLATION_FAILED" }, separator }
|
|
|
|
local ref = self.ref
|
|
local i = self.batch_i + 1
|
|
-- Due to network saturation concerns, only group five strings together
|
|
-- See https://github.com/factoriolib/flib/issues/45
|
|
if i < 5 then
|
|
ref[i] = to_add --- @diagnostic disable-line
|
|
self.batch_i = i
|
|
else
|
|
local s_i = self.dict_i + 1
|
|
self.dict_i = s_i
|
|
local new_set = { "", to_add }
|
|
self.ref = new_set
|
|
self.strings[s_i] = new_set
|
|
self.batch_i = 2
|
|
end
|
|
self.total = self.total + 1
|
|
raw._total_strings = raw._total_strings + 1
|
|
end
|
|
|
|
--- @class RawDictionary
|
|
function flib_dictionary.new(name, keep_untranslated, initial_contents)
|
|
if raw[name] then
|
|
error("Dictionary with the name `" .. name .. "` already exists.")
|
|
end
|
|
|
|
--- @type LocalisedString
|
|
local initial_string = { "" }
|
|
--- @class RawDictionary
|
|
local self = {
|
|
-- Indices
|
|
batch_i = 1,
|
|
dict_i = 1,
|
|
total = 1,
|
|
-- Internal
|
|
ref = initial_string,
|
|
--- @type LocalisedString
|
|
strings = { initial_string },
|
|
-- Meta
|
|
name = name,
|
|
}
|
|
setmetatable(self, { __index = RawDictionary })
|
|
|
|
for key, value in pairs(initial_contents or {}) do
|
|
self:add(key, value)
|
|
end
|
|
--- @diagnostic disable-next-line
|
|
raw[name] = { strings = self.strings, keep_untranslated = keep_untranslated }
|
|
|
|
return self
|
|
end
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function flib_dictionary.init()
|
|
if not global.__flib then
|
|
global.__flib = {}
|
|
end
|
|
global.__flib.dictionary = {
|
|
in_process = {},
|
|
players = {},
|
|
raw = { _total_strings = 0 },
|
|
translated = {},
|
|
}
|
|
if use_local_storage then
|
|
raw = { _total_strings = 0 }
|
|
else
|
|
raw = global.__flib.dictionary.raw
|
|
end
|
|
end
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function flib_dictionary.load()
|
|
if not use_local_storage and global.__flib and global.__flib.dictionary then
|
|
raw = global.__flib.dictionary.raw
|
|
end
|
|
end
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function flib_dictionary.translate(player)
|
|
if not player.connected then
|
|
error("Player must be connected to the game before this function can be called!")
|
|
end
|
|
|
|
local player_data = global.__flib.dictionary.players[player.index]
|
|
if player_data then
|
|
return
|
|
end
|
|
global.__flib.dictionary.players[player.index] = {
|
|
player = player,
|
|
status = "get_language",
|
|
requested_tick = game.tick,
|
|
}
|
|
|
|
player.request_translation({ "", "FLIB_LOCALE_IDENTIFIER", separator, { "locale-identifier" } })
|
|
end
|
|
|
|
local function request_translation(player_data)
|
|
local string = raw[player_data.dictionary].strings[player_data.i]
|
|
|
|
-- We use `while` instead of `if` here just in case a dictionary doesn't have any strings in it
|
|
while not string do
|
|
local next_dictionary = next(raw, player_data.dictionary)
|
|
if next_dictionary then
|
|
-- Set the next dictionary and reset index
|
|
player_data.dictionary = next_dictionary
|
|
player_data.i = 1
|
|
string = raw[next_dictionary].strings[1]
|
|
else
|
|
-- We're done!
|
|
player_data.status = "finished"
|
|
return
|
|
end
|
|
end
|
|
|
|
player_data.player.request_translation({
|
|
"",
|
|
key_value("FLIB_DICTIONARY_MOD", script.mod_name),
|
|
key_value("FLIB_DICTIONARY_NAME", player_data.dictionary),
|
|
key_value("FLIB_DICTIONARY_LANGUAGE", player_data.language),
|
|
key_value("FLIB_DICTIONARY_STRING_INDEX", player_data.i),
|
|
string,
|
|
})
|
|
|
|
player_data.requested_tick = game.tick
|
|
end
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function flib_dictionary.check_skipped()
|
|
local script_data = global.__flib.dictionary
|
|
local tick = game.tick
|
|
for _, player_data in pairs(script_data.players) do
|
|
-- If it's been longer than the timeout, request the string again
|
|
-- This is to solve a very rare edge case where translations requested on the same tick that a singleplayer game
|
|
-- is saved will not be returned when that save is loaded
|
|
if (player_data.requested_tick or 0) + translation_timeout <= tick then
|
|
if player_data.status == "get_language" then
|
|
player_data.player.request_translation({ "", "FLIB_LOCALE_IDENTIFIER", separator, { "locale-identifier" } })
|
|
end
|
|
if player_data.status == "translating" then
|
|
request_translation(player_data)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Escape match special characters
|
|
local function match_literal(s)
|
|
return string.gsub(s, "%-", "%%-")
|
|
end
|
|
|
|
--- @param dict_lang string
|
|
local function clean_gui(dict_lang)
|
|
for _, player in pairs(game.players) do
|
|
local window = mod_gui.get_frame_flow(player).flib_translation_progress
|
|
if window then
|
|
local pane = window.pane
|
|
local mod_flow = pane[script.mod_name]
|
|
if mod_flow then
|
|
local lang_flow = mod_flow[dict_lang]
|
|
if lang_flow then
|
|
lang_flow.destroy()
|
|
end
|
|
if #mod_flow.children == 1 then
|
|
mod_flow.destroy()
|
|
end
|
|
end
|
|
if #pane.children == 0 then
|
|
window.destroy()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local dictionary_match_string = key_value("^FLIB_DICTIONARY_MOD", match_literal(script.mod_name))
|
|
.. key_value("FLIB_DICTIONARY_NAME", "(.-)")
|
|
.. key_value("FLIB_DICTIONARY_LANGUAGE", "(.-)")
|
|
.. key_value("FLIB_DICTIONARY_STRING_INDEX", "(%d-)")
|
|
.. "(.*)$"
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function flib_dictionary.process_translation(event_data)
|
|
if not event_data.translated then
|
|
return
|
|
end
|
|
local script_data = global.__flib.dictionary
|
|
if string.find(event_data.result, "FLIB_DICTIONARY_NAME") then
|
|
local _, _, dict_name, dict_lang, string_index, translation =
|
|
string.find(event_data.result, dictionary_match_string)
|
|
|
|
if dict_name and dict_lang and string_index and translation then
|
|
local language_data = script_data.in_process[dict_lang]
|
|
-- In some cases, this can fire before on_configuration_changed
|
|
if not language_data then
|
|
return
|
|
end
|
|
local dictionary = language_data.dictionaries[dict_name]
|
|
if not dictionary then
|
|
return
|
|
end
|
|
local dict_data = raw[dict_name]
|
|
local player_data = script_data.players[event_data.player_index]
|
|
|
|
-- If this number does not match, this is a duplicate, so ignore it
|
|
if tonumber(string_index) == player_data.i then
|
|
-- Extract current string's translations
|
|
for str in string.gmatch(translation, "(.-)" .. separator) do
|
|
local _, _, key, value = string.find(str, "^(.-)" .. inner_separator .. "(.-)$")
|
|
if key then
|
|
-- If `keep_untranslated` is true, then use the key as the value if it failed
|
|
local failed = string.find(value, "FLIB_TRANSLATION_FAILED")
|
|
if failed and dict_data.keep_untranslated then
|
|
value = key
|
|
elseif failed then
|
|
value = nil
|
|
end
|
|
if value then
|
|
dictionary[key] = value
|
|
end
|
|
language_data.translated_i = language_data.translated_i + 1
|
|
end
|
|
end
|
|
|
|
-- Request next translation
|
|
player_data.i = player_data.i + 1
|
|
request_translation(player_data)
|
|
|
|
-- GUI
|
|
for _, player in pairs(game.players) do
|
|
--- @type LuaGuiElement
|
|
local flow = mod_gui.get_frame_flow(player)
|
|
if not flow.flib_translation_progress then
|
|
gui.add(flow, {
|
|
type = "frame",
|
|
name = "flib_translation_progress",
|
|
style = mod_gui.frame_style,
|
|
style_mods = { width = 350 },
|
|
direction = "vertical",
|
|
{
|
|
type = "label",
|
|
style = "frame_title",
|
|
caption = { "gui.flib-translating-dictionaries" },
|
|
tooltip = { "gui.flib-translating-dictionaries-description" },
|
|
},
|
|
{
|
|
type = "frame",
|
|
name = "pane",
|
|
style = "inside_shallow_frame_with_padding",
|
|
style_mods = { top_padding = 8 },
|
|
direction = "vertical",
|
|
},
|
|
})
|
|
end
|
|
local pane = flow.flib_translation_progress.pane --[[@as LuaGuiElement]]
|
|
if not pane[script.mod_name] then
|
|
gui.add(pane, {
|
|
type = "flow",
|
|
name = script.mod_name,
|
|
direction = "vertical",
|
|
{ type = "label", style = "caption_label", style_mods = { top_margin = 4 }, caption = script.mod_name },
|
|
})
|
|
end
|
|
local mod_flow = pane[script.mod_name] --[[@as LuaGuiElement]]
|
|
if not mod_flow[dict_lang] then
|
|
gui.add(mod_flow, {
|
|
type = "flow",
|
|
name = dict_lang,
|
|
style_mods = { vertical_align = "center", horizontal_spacing = 8 },
|
|
{ type = "label", style = "bold_label", caption = dict_lang },
|
|
{ type = "progressbar", name = "bar", style_mods = { horizontally_stretchable = true } },
|
|
{ type = "label", name = "label", style = "bold_label" },
|
|
})
|
|
end
|
|
local progress = language_data.translated_i / raw._total_strings
|
|
mod_flow[dict_lang].bar.value = progress --[[@as double]]
|
|
mod_flow[dict_lang].label.caption = tostring(math.ceil(progress * 100)) .. "%"
|
|
mod_flow[dict_lang].label.tooltip = dict_name
|
|
.. "\n"
|
|
.. language_data.translated_i
|
|
.. " / "
|
|
.. raw._total_strings
|
|
end
|
|
|
|
if player_data.status == "finished" then
|
|
-- Clean up translation data
|
|
script_data.translated[dict_lang] = language_data.dictionaries
|
|
script_data.in_process[dict_lang] = nil
|
|
for _, player_index in pairs(language_data.players) do
|
|
script_data.players[player_index] = nil
|
|
end
|
|
|
|
-- Clean up GUI
|
|
clean_gui(dict_lang)
|
|
|
|
return { dictionaries = language_data.dictionaries, language = dict_lang, players = language_data.players }
|
|
end
|
|
end
|
|
end
|
|
elseif string.find(event_data.result, "^FLIB_LOCALE_IDENTIFIER") then
|
|
local _, _, language = string.find(event_data.result, "^FLIB_LOCALE_IDENTIFIER" .. separator .. "(.*)$")
|
|
if language then
|
|
local player_data = script_data.players[event_data.player_index]
|
|
-- Handle duplicates
|
|
if not player_data or player_data.status ~= "get_language" then
|
|
return
|
|
end
|
|
|
|
player_data.language = language
|
|
|
|
-- Check if this language is already translated or being translated
|
|
local dictionaries = script_data.translated[language]
|
|
if dictionaries then
|
|
script_data.players[event_data.player_index] = nil
|
|
return { dictionaries = dictionaries, language = language, players = { event_data.player_index } }
|
|
end
|
|
local in_process = script_data.in_process[language]
|
|
if in_process then
|
|
table.insert(in_process.players, event_data.player_index)
|
|
player_data.status = "waiting"
|
|
return
|
|
end
|
|
|
|
-- Set up player data for translating
|
|
player_data.status = "translating"
|
|
player_data.dictionary = next(raw, "_total_strings")
|
|
player_data.i = 1
|
|
|
|
-- Add language to in process data
|
|
script_data.in_process[language] = {
|
|
dictionaries = table.map(raw, function(_, k)
|
|
if k ~= "_total_strings" then
|
|
return {}
|
|
end
|
|
end),
|
|
players = { event_data.player_index },
|
|
translated_i = 0,
|
|
}
|
|
|
|
-- Start translating
|
|
request_translation(player_data)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function flib_dictionary.cancel_translation(player_index)
|
|
local script_data = global.__flib.dictionary
|
|
local player_data = script_data.players[player_index]
|
|
if not player_data then
|
|
return
|
|
end
|
|
-- Delete this player's data from global
|
|
script_data.players[player_index] = nil
|
|
|
|
local in_process = script_data.in_process[player_data.language]
|
|
if not in_process then
|
|
return
|
|
end
|
|
|
|
-- Remove this player from the players table
|
|
local i = table.find(in_process.players, player_index)
|
|
if i then
|
|
table.remove(in_process.players, i)
|
|
end
|
|
|
|
if player_data.status ~= "translating" then
|
|
return
|
|
end
|
|
|
|
-- Find the next player in the list with valid data
|
|
local next_player_data
|
|
for _, player_index in pairs(in_process.players) do
|
|
local player_data = script_data.players[player_index]
|
|
if player_data then
|
|
next_player_data = player_data
|
|
break
|
|
end
|
|
end
|
|
-- If there are no more valid players
|
|
if not next_player_data then
|
|
-- Completely cancel the translation
|
|
script_data.in_process[player_data.language] = nil
|
|
clean_gui(player_data.language)
|
|
return
|
|
end
|
|
--Update player info
|
|
next_player_data.status = "translating"
|
|
next_player_data.dictionary = player_data.dictionary
|
|
next_player_data.i = player_data.i
|
|
-- Resume translating with the new player
|
|
request_translation(next_player_data)
|
|
end
|
|
|
|
--- @deprecated Use 'dictionary-lite' instead.
|
|
function flib_dictionary.set_use_local_storage(value)
|
|
use_local_storage = value
|
|
end
|
|
|
|
return flib_dictionary
|