material-maker/material_maker/locale/generate_po_template.gd

382 lines
13 KiB
GDScript

tool
extends EditorScript
class TranslationString:
var string : String = ""
var references : Array = []
class TranslationStrings:
var strings_indexes : Dictionary = {}
var strings : Array = []
var word_regex : RegEx
var tr_regex : RegEx
var node_regex : RegEx
var menu_regex : RegEx
var panel_regex : RegEx
var tscn_regex : RegEx
func _init():
word_regex = RegEx.new()
word_regex.compile("[a-zA-Z]{2}")
tr_regex = RegEx.new()
tr_regex.compile("tr\\s*\\(\"(.*?)\"\\)")
node_regex = RegEx.new()
node_regex.compile("\\[node name=\"(.*)\" type=\"(.*)\" parent=\"(.*)\"\\]")
menu_regex = RegEx.new()
menu_regex.compile("menu=\"([^\"]*?)\"")
panel_regex = RegEx.new()
panel_regex.compile("name=\"([^\"]*?)\".*scene=")
tscn_regex = RegEx.new()
tscn_regex.compile("([a-z_]+)\\s*=\\s*\"(.*)\"")
func read_language_file(fn : String):
var input_translation : Translation
match fn.get_extension():
"csv":
print("Reading %s" % fn)
input_translation = read_language_file_csv(fn)
"po":
print("Reading %s" % fn)
input_translation = load(fn)
_:
print("Unsupported translation file %s" % fn)
return
var translation : Translation = Translation.new()
translation.locale = input_translation.locale
var translated_string : Array = []
for s in strings:
if input_translation.get_message(s.string) != "":
translated_string.push_back(s.string)
translation.add_message(s.string, input_translation.get_message(s.string))
else:
var found : bool = false
for k in input_translation.get_message_list():
if s.string.to_lower() == k.to_lower():
found = true
translated_string.push_back(k)
translation.add_message(s.string, input_translation.get_message(k))
break
if !found:
found = true
var t : PoolStringArray = PoolStringArray()
for subs in s.string.split("\n"):
if input_translation.get_message(subs) != "":
t.push_back(input_translation.get_message(subs))
translated_string.push_back(subs)
else:
found = false
break
if found:
translation.add_message(s.string, t.join("\n"))
else:
pass
#print("no translation for '%s'" % s.string)
return translation
func read_language_file_csv(fn : String):
var input_translation : Translation = Translation.new()
var f : File = File.new()
if f.open(fn, File.READ) != OK:
print("Error")
return input_translation
var count : int = 0
var l = f.get_line()
var sep_char = l[2]
input_translation.locale = l.split(sep_char)[1]
while !f.eof_reached():
l = f.get_line()
var line
for sep in [ "\""+sep_char+"\"", "\""+sep_char, sep_char+"\"", sep_char ]:
line = l.split(sep)
if line.size() == 2:
if sep[0] == "\"":
line[0] = line[0].right(1)
if sep[sep.length()-1] == "\"":
line[1] = line[1].left(line[1].length()-1)
break
if line.size() == 2:
input_translation.add_message(line[0].replace("\\n", "\n"), line[1].replace("\\n", "\n"))
count += 1
else:
pass
#print(line)
#print(line.size())
f.close()
print("Extracted %d strings from %s" % [ count, fn ])
return input_translation
func save_csv(fn : String, translation : Translation = null):
if translation == null:
return
var f : File = File.new()
if f.open(fn, File.WRITE) == OK:
f.store_line("id|"+translation.locale)
for s in strings:
f.store_line("%s|%s" % [ s.string.replace("\n", "\\n"), translation.get_message(s.string).replace("\n", "\\n") ])
f.close()
if f.open(fn+".report", File.WRITE) == OK:
var string_list = []
for s in strings:
string_list.push_back(s.string)
f.store_line("Missing strings:")
for s in string_list:
if translation.get_message_list().find(s) == -1:
f.store_line("- "+s)
f.store_line("Extra strings:")
for s in translation.get_message_list():
if ! (s in string_list):
f.store_line("- "+s)
f.close()
func save(fn : String, translation = null):
var f : File = File.new()
if f.open(fn, File.WRITE) == OK:
f.store_line("# Translations template for Material Maker.")
f.store_line("# Copyright (C) 2018-2022 Rodolphe Suescun and contributors")
f.store_line("# This file is distributed under the same license as the Material Maker project.")
f.store_line("# Rodolphe Suescun <rodzilla@free.fr>, 2022.")
f.store_line("#")
f.store_line("#, fuzzy")
f.store_line("msgid \"\"")
f.store_line("msgstr \"\"")
f.store_line("\"Project-Id-Version: PROJECT VERSION\\n\"")
f.store_line("\"Report-Msgid-Bugs-To: rodzilla@free.fr\\n\"")
var dt = OS.get_datetime()
f.store_line("\"POT-Creation-Date: %04d-%02d-%02d %02d:%02d\\n\"" % [ dt.year, dt.month, dt.day, dt.hour, dt.minute ])
f.store_line("\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"")
f.store_line("\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"")
f.store_line("\"Language-Team: LANGUAGE <LL@li.org>\\n\"")
f.store_line("\"MIME-Version: 1.0\\n\"")
f.store_line("\"Content-Type: text/plain; charset=utf-8\\n\"")
f.store_line("\"Content-Transfer-Encoding: 8bit\\n\"")
f.store_line("\"Generated-By: Babel 2.9.0\\n\"")
f.store_line("")
for s in strings:
for r in s.references:
f.store_line("#: "+r)
f.store_line("msgid \""+s.string.replace("\n", "\\n")+"\"")
if translation != null:
f.store_line("msgstr \""+translation.get_message(s.string).replace("\n", "\\n")+"\"")
else:
f.store_line("msgstr \"\"")
f.store_line("")
f.close()
func add_string(s : String, r : String) -> bool:
if word_regex.search(s) == null:
return false
var string : TranslationString
var added : bool = false
if strings_indexes.has(s):
string = strings[strings_indexes[s]]
else:
string = TranslationString.new()
string.string = s
strings_indexes[s] = strings.size()
strings.push_back(string)
added = true
if string.references.find(r) == -1:
string.references.push_back(r)
return added
func extract_strings_from_gd(fn):
var string_count : int = 0
var f : File = File.new()
if f.open(fn, File.READ) == OK:
var line_number = 1
while ! f.eof_reached():
var l : String = f.get_line()
var result = tr_regex.search_all(l)
if !result.empty():
for r in result:
if add_string(r.strings[1], fn+":"+str(line_number)):
string_count += 1
else:
# extract menus from code (Material Maker specific)
result = menu_regex.search(l)
if result != null:
for m in result.strings[1].split("/"):
if add_string(m, "Menu in "+fn):
string_count += 1
# extract panels from code (Material Maker specific)
else:
result = panel_regex.search(l)
if result != null and add_string(result.strings[1], "Panel name in "+fn+":"+str(line_number)):
string_count += 1
line_number += 1
return string_count
func extract_strings_from_tscn(fn):
var string_count : int = 0
var f : File = File.new()
if f.open(fn, File.READ) == OK:
var line_number = 1
var tab_containers : Array = []
while ! f.eof_reached():
var l : String = f.get_line()
var result = tscn_regex.search(l)
if result != null:
if result.strings[1] in [ "text", "tooltip_hint" ] and add_string(result.strings[2], fn+":"+str(line_number)):
string_count += 1
result = node_regex.search(l)
if result != null:
if result.strings[2] == "TabContainer":
var tab_container
if result.strings[3] == ".":
tab_container = result.strings[1]
else:
tab_container = "%s/%s" % [ result.strings[3], result.strings[1] ]
tab_containers.push_back(tab_container)
elif result.strings[3] in tab_containers and add_string(result.strings[1], fn+":"+str(line_number)):
string_count += 1
line_number += 1
return string_count
const FIELDS : Dictionary = {
"parameters": [ "label", "shortdesc", "longdesc" ],
"inputs": [ "label", [ "shortdesc", "name" ], "longdesc" ],
"outputs": [ "shortdesc", "longdesc" ]
}
func extract_strings_from_mmg(fn):
var string_count : int = 0
var f : File = File.new()
if f.open(fn, File.READ) == OK:
var json_parse_result : JSONParseResult = JSON.parse(f.get_as_text())
if json_parse_result != null:
var json = json_parse_result.result
if json.has("type"):
match json.type:
"graph":
if json.has("label") and add_string(json.label, fn):
string_count += 1
if json.has("shortdesc") and add_string(json.shortdesc, fn):
string_count += 1
if json.has("longdesc") and add_string(json.longdesc, fn):
string_count += 1
for n in json.nodes:
match n.name:
"gen_parameters":
if n.has("widgets"):
for x in n.widgets:
for field in [ "name", "shortdesc", "longdesc" ]:
if x.has(field) and add_string(x[field], fn):
string_count += 1
"gen_inputs", "gen_outputs":
if n.has("ports"):
for x in n.ports:
for field in [ "name", "shortdesc", "longdesc" ]:
if x.has(field) and add_string(x[field], fn):
string_count += 1
"material_export","shader","brush":
if json.has("shader_model"):
if json.shader_model.has("name") and add_string(json.shader_model.name, fn):
string_count += 1
if json.shader_model.has("shortdesc") and add_string(json.shader_model.shortdesc, fn):
string_count += 1
if json.shader_model.has("longdesc") and add_string(json.shader_model.longdesc, fn):
string_count += 1
for t in FIELDS.keys():
if json.shader_model.has(t):
for x in json.shader_model[t]:
for field in FIELDS[t]:
if field is String:
if x.has(field) and add_string(x[field], fn):
string_count += 1
elif field is Array:
for field1 in field:
if x.has(field1) and add_string(x[field1], fn):
string_count += 1
break
if x.has("values"):
for v in x.values:
if v.has("name") and add_string(v.name, fn):
string_count += 1
else:
print(t+" not found")
_:
print(json.type)
else:
print(json)
return string_count
func extract_strings_from_library(fn):
var string_count : int = 0
var f : File = File.new()
if f.open(fn, File.READ) == OK:
var json_parse_result : JSONParseResult = JSON.parse(f.get_as_text())
if json_parse_result != null:
var json = json_parse_result.result
if json.has("name") and add_string(json.name, fn):
string_count += 1
if json.has("lib"):
for i in json.lib:
if i.has("tree_item"):
for n in i.tree_item.split("/"):
if add_string(n, fn):
string_count += 1
return string_count
func find_files(path, extensions):
var file_names : Array = []
var dir = Directory.new()
if dir.open(path) == OK:
var dir_names : Array = []
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if dir.current_is_dir() and file_name[0] != ".":
dir_names.push_back(path+"/"+file_name)
elif file_name.get_extension() in extensions:
file_names.push_back(path+"/"+file_name)
file_name = dir.get_next()
file_names.sort()
dir_names.sort()
for d in dir_names:
file_names.append_array(find_files(d, extensions))
return file_names
func _run():
var ts : TranslationStrings = TranslationStrings.new()
var string_count : int = 0
print("Extracting strings from scenes and scripts")
for f in find_files("res://", [ "gd", "tscn" ]):
match f.get_extension():
"gd":
string_count += ts.extract_strings_from_gd(f)
"tscn":
string_count += ts.extract_strings_from_tscn(f)
ts.save("res://material_maker/locale/material-maker.pot")
print("Extracting strings from predefined nodes")
for f in find_files("res://addons/material_maker", [ "mmg" ]):
string_count += ts.extract_strings_from_mmg(f)
print("Extracting strings from libraries")
for f in find_files("res://material_maker/library", [ "json" ]):
string_count += ts.extract_strings_from_library(f)
ts.save("res://material_maker/locale/material-maker-all.pot")
var dir : Directory = Directory.new()
var path = "res://material_maker/locale/translations"
if dir.open("res://material_maker/locale/translations") == OK:
var languages : Dictionary = {}
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
match file_name.get_extension():
"csv", "po", "translation":
var language = file_name.get_basename()
if ! languages.has(language) or file_name.get_extension() != "csv":
languages[language] = path.plus_file(file_name)
file_name = dir.get_next()
for l in languages.keys():
print("Processing %s" % l)
var in_file : String = languages[l]
var out_file : String = languages[l].get_basename()+".csv"
print("%s -> %s" % [ in_file, out_file ])
ts.save_csv(out_file, ts.read_language_file(in_file))