feat(locale): configure Chinese (zh) localization for client and align with 40250 server
This commit is contained in:
@@ -29,7 +29,7 @@ func _init() -> void:
|
||||
_mock = "--mock" in OS.get_cmdline_user_args() or "--mock" in OS.get_cmdline_args()
|
||||
|
||||
_loc = Locale.new()
|
||||
_loc.setup(_assets, "en")
|
||||
_loc.setup(_assets, Locale.get_default_lang())
|
||||
|
||||
_audio = Audio.new()
|
||||
get_root().add_child(_audio)
|
||||
|
||||
+10
-4
@@ -458,7 +458,11 @@ func setup(m2client: Node, assets_root: String,
|
||||
if ClassDB.class_exists("Metin2Proto") and assets_root != "":
|
||||
proto = ClassDB.instantiate("Metin2Proto")
|
||||
add_child(proto)
|
||||
var ip := assets_root.path_join("locale/locale/en/item_proto")
|
||||
var LocaleScript = load("res://locale.gd")
|
||||
var cur_lang: String = LocaleScript.get_default_lang() if LocaleScript and LocaleScript.has_method("get_default_lang") else "zh"
|
||||
var ip := assets_root.path_join("locale/locale/%s/item_proto" % cur_lang)
|
||||
if not FileAccess.file_exists(ip):
|
||||
ip = assets_root.path_join("locale/locale/en/item_proto")
|
||||
if FileAccess.file_exists(ip):
|
||||
proto.call("load_item_proto", ip)
|
||||
if quickbar and "proto" in quickbar:
|
||||
@@ -468,7 +472,9 @@ func setup(m2client: Node, assets_root: String,
|
||||
chat.set_proto(proto)
|
||||
if quest_dialog and quest_dialog.has_method("set_proto"):
|
||||
quest_dialog.set_proto(proto)
|
||||
var mp := assets_root.path_join("locale/locale/en/mob_proto")
|
||||
var mp := assets_root.path_join("locale/locale/%s/mob_proto" % cur_lang)
|
||||
if not FileAccess.file_exists(mp):
|
||||
mp = assets_root.path_join("locale/locale/en/mob_proto")
|
||||
if FileAccess.file_exists(mp):
|
||||
proto.call("load_mob_proto", mp)
|
||||
await _yield()
|
||||
@@ -480,7 +486,7 @@ func setup(m2client: Node, assets_root: String,
|
||||
if net_world and "title_name_resolver" in net_world and assets_root != "":
|
||||
# §8.8 称号名:introloading.__RegisterTitleName -> localeInfo.PVP_LEVEL<grade>
|
||||
var _tt_loc: RefCounted = load("res://locale.gd").new()
|
||||
_tt_loc.setup(assets_root, "en")
|
||||
_tt_loc.setup(assets_root, cur_lang)
|
||||
net_world.title_name_resolver = func(g: int) -> String:
|
||||
var k := "PVP_LEVEL%d" % g
|
||||
return _tt_loc.t(k) if _tt_loc.has(k) else ""
|
||||
@@ -515,7 +521,7 @@ func setup(m2client: Node, assets_root: String,
|
||||
var _possessive := ""
|
||||
if assets_root != "":
|
||||
var _poss_loc: RefCounted = load("res://locale.gd").new()
|
||||
_poss_loc.setup(assets_root, "en")
|
||||
_poss_loc.setup(assets_root, cur_lang)
|
||||
if _poss_loc.has("POSSESSIVE_MORPHENE"):
|
||||
_possessive = _poss_loc.t("POSSESSIVE_MORPHENE")
|
||||
ground_items.setup(client, _mount, func() -> Node: return player, proto, item_list,
|
||||
|
||||
+54
-2
@@ -3,23 +3,75 @@
|
||||
# 用 preload("res://locale.gd").new();setup() 后 t("KEY", [args]).
|
||||
extends RefCounted
|
||||
|
||||
var _lang := "en"
|
||||
var _lang := "zh"
|
||||
var _tab := {} # KEY -> value
|
||||
var _item_names := {} # int vnum -> String name
|
||||
var _mob_names := {} # int vnum -> String name
|
||||
var _base := ""
|
||||
|
||||
func setup(assets_root: String, lang := "en") -> void:
|
||||
static func get_default_lang() -> String:
|
||||
var env_lang := OS.get_environment("MT_LOCALE").strip_edges().to_lower()
|
||||
if not env_lang.is_empty():
|
||||
return env_lang
|
||||
return "zh"
|
||||
|
||||
func setup(assets_root: String, lang := "") -> void:
|
||||
_base = assets_root.path_join("locale/locale")
|
||||
if lang.is_empty():
|
||||
lang = get_default_lang()
|
||||
set_lang(lang)
|
||||
|
||||
func set_lang(lang: String) -> void:
|
||||
_lang = lang
|
||||
_tab.clear()
|
||||
_item_names.clear()
|
||||
_mob_names.clear()
|
||||
var dir := _base.path_join(lang)
|
||||
if not DirAccess.dir_exists_absolute(dir):
|
||||
push_warning("[locale] no such lang: " + dir)
|
||||
return
|
||||
for f in ["locale_interface.txt", "locale_game.txt", "itemdesc.txt", "skilldesc.txt"]:
|
||||
_load_table(dir.path_join(f))
|
||||
_load_name_table(dir.path_join("item_names.txt"), _item_names)
|
||||
_load_name_table(dir.path_join("mob_names.txt"), _mob_names)
|
||||
|
||||
func _load_name_table(path: String, target_dict: Dictionary) -> void:
|
||||
if not FileAccess.file_exists(path):
|
||||
return
|
||||
var fa := FileAccess.open(path, FileAccess.READ)
|
||||
if fa == null:
|
||||
return
|
||||
while not fa.eof_reached():
|
||||
var line := fa.get_line().strip_edges()
|
||||
if line.is_empty() or line.begins_with("#") or line.begins_with("VNUM"):
|
||||
continue
|
||||
var tab := line.find("\t")
|
||||
if tab < 0:
|
||||
tab = line.find(" ")
|
||||
if tab < 0:
|
||||
continue
|
||||
var k_str := line.substr(0, tab).strip_edges()
|
||||
var val := line.substr(tab + 1).strip_edges()
|
||||
if k_str.is_valid_int():
|
||||
var vnum := int(k_str)
|
||||
target_dict[vnum] = val
|
||||
_tab["ITEM_%d" % vnum] = val
|
||||
|
||||
func item_name(vnum: int, fallback: String = "") -> String:
|
||||
if _item_names.has(vnum):
|
||||
return String(_item_names[vnum])
|
||||
return fallback if not fallback.is_empty() else ("物品 #%d" % vnum)
|
||||
|
||||
func mob_name(vnum: int, fallback: String = "") -> String:
|
||||
if _mob_names.has(vnum):
|
||||
return String(_mob_names[vnum])
|
||||
return fallback if not fallback.is_empty() else ("怪物 #%d" % vnum)
|
||||
|
||||
func has_item_name(vnum: int) -> bool:
|
||||
return _item_names.has(vnum)
|
||||
|
||||
func has_mob_name(vnum: int) -> bool:
|
||||
return _mob_names.has(vnum)
|
||||
|
||||
func _load_table(path: String) -> void:
|
||||
if not FileAccess.file_exists(path):
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ var _t := 0.0
|
||||
func _init() -> void:
|
||||
_assets = AssetRoot.path()
|
||||
_loc = Locale.new()
|
||||
_loc.setup(_assets, "en")
|
||||
_loc.setup(_assets, Locale.get_default_lang())
|
||||
_audio = Audio.new()
|
||||
get_root().add_child(_audio)
|
||||
_audio.setup(_assets)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
extends SceneTree
|
||||
|
||||
const Locale = preload("res://locale.gd")
|
||||
const ItemTooltip = preload("res://ui/item_tooltip.gd")
|
||||
|
||||
func _init() -> void:
|
||||
print("=== Starting test_chinese_locale ===")
|
||||
var assets := AssetRoot.path()
|
||||
var passed := 0
|
||||
var failed := 0
|
||||
|
||||
# 1. Default language
|
||||
var default_lang := Locale.get_default_lang()
|
||||
if default_lang == "zh":
|
||||
print("PASS: Default language is 'zh'")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: Default language expected 'zh', got '%s'" % default_lang)
|
||||
failed += 1
|
||||
|
||||
# 2. Locale setup
|
||||
var loc = Locale.new()
|
||||
loc.setup(assets, "zh")
|
||||
if loc.lang() == "zh":
|
||||
print("PASS: Locale lang is 'zh'")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: Locale lang expected 'zh', got '%s'" % loc.lang())
|
||||
failed += 1
|
||||
|
||||
# 3. Item names
|
||||
var sword_name := loc.item_name(10)
|
||||
if sword_name == "武士刀+0":
|
||||
print("PASS: Item 10 name is 武士刀+0")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: Item 10 name expected '武士刀+0', got '%s'" % sword_name)
|
||||
failed += 1
|
||||
|
||||
var yang_name := loc.item_name(1)
|
||||
if yang_name == "金币":
|
||||
print("PASS: Item 1 name is 金币")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: Item 1 name expected '金币', got '%s'" % yang_name)
|
||||
failed += 1
|
||||
|
||||
var dress_name := loc.item_name(11901)
|
||||
if dress_name == "礼服":
|
||||
print("PASS: Item 11901 name is 礼服")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: Item 11901 name expected '礼服', got '%s'" % dress_name)
|
||||
failed += 1
|
||||
|
||||
# 4. Mob names
|
||||
var dog_name := loc.mob_name(101)
|
||||
if dog_name == "幼狼":
|
||||
print("PASS: Mob 101 name is 幼狼")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: Mob 101 name expected '幼狼', got '%s'" % dog_name)
|
||||
failed += 1
|
||||
|
||||
# 5. System messages
|
||||
var arrow_msg := loc.t("CANNOT_SHOOT_EMPTY_ARROW")
|
||||
if arrow_msg == "没有箭了,请装备箭":
|
||||
print("PASS: CANNOT_SHOOT_EMPTY_ARROW translation matches")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: CANNOT_SHOOT_EMPTY_ARROW expected '没有箭了,请装备箭', got '%s'" % arrow_msg)
|
||||
failed += 1
|
||||
|
||||
var align_msg := loc.t("ALIGNMENT_NAME")
|
||||
if align_msg == "善恶值 :":
|
||||
print("PASS: ALIGNMENT_NAME translation matches")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: ALIGNMENT_NAME expected '善恶值 :', got '%s'" % align_msg)
|
||||
failed += 1
|
||||
|
||||
# 6. ItemTooltip integration
|
||||
var tt = ItemTooltip.new()
|
||||
var tt_ok := tt.setup(assets, "zh")
|
||||
if tt_ok:
|
||||
print("PASS: ItemTooltip setup succeeded for zh")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: ItemTooltip setup failed")
|
||||
failed += 1
|
||||
|
||||
var model := tt.build_model(10, 1, {})
|
||||
if String(model.get("title", "")) == "武士刀+0":
|
||||
print("PASS: ItemTooltip build_model resolved title 武士刀+0 without proto")
|
||||
passed += 1
|
||||
else:
|
||||
printerr("FAIL: Expected title '武士刀+0', got '%s'" % model.get("title", ""))
|
||||
failed += 1
|
||||
|
||||
print("\n=== %d / %d CHINESE LOCALE TESTS PASSED (Failed: %d) ===" % [passed, passed + failed, failed])
|
||||
if failed > 0:
|
||||
quit(1)
|
||||
else:
|
||||
quit(0)
|
||||
@@ -257,15 +257,40 @@ const PERCENT_APPLIES := {
|
||||
}
|
||||
|
||||
var _descriptions: Dictionary = {}
|
||||
var _item_names: Dictionary = {}
|
||||
var _skill_names: Dictionary = {}
|
||||
var _proto_node: Object = null
|
||||
var _assets_root: String = ""
|
||||
|
||||
func setup(assets_root: String, lang := "en", proto_node: Object = null) -> bool:
|
||||
func setup(assets_root: String, lang := "", proto_node: Object = null) -> bool:
|
||||
_descriptions.clear()
|
||||
_item_names.clear()
|
||||
_skill_names.clear()
|
||||
_assets_root = assets_root
|
||||
_proto_node = proto_node
|
||||
if lang.is_empty():
|
||||
var LocaleScript = load("res://locale.gd")
|
||||
if LocaleScript and LocaleScript.has_method("get_default_lang"):
|
||||
lang = LocaleScript.get_default_lang()
|
||||
else:
|
||||
lang = "zh"
|
||||
var names_path := assets_root.path_join("locale/locale/%s/item_names.txt" % lang)
|
||||
if FileAccess.file_exists(names_path):
|
||||
var nf := FileAccess.open(names_path, FileAccess.READ)
|
||||
if nf:
|
||||
while not nf.eof_reached():
|
||||
var line := nf.get_line().strip_edges()
|
||||
if line.is_empty() or line.begins_with("#") or line.begins_with("VNUM"):
|
||||
continue
|
||||
var tab := line.find("\t")
|
||||
if tab < 0:
|
||||
tab = line.find(" ")
|
||||
if tab < 0:
|
||||
continue
|
||||
var k := line.substr(0, tab).strip_edges()
|
||||
var v := line.substr(tab + 1).strip_edges()
|
||||
if k.is_valid_int():
|
||||
_item_names[int(k)] = v
|
||||
var path := assets_root.path_join("locale/locale/%s/itemdesc.txt" % lang)
|
||||
if FileAccess.file_exists(path):
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
@@ -282,27 +307,40 @@ func setup(assets_root: String, lang := "en", proto_node: Object = null) -> bool
|
||||
"description": parts[2].strip_edges() if parts.size() > 2 else "",
|
||||
}
|
||||
_load_skill_names(assets_root, lang)
|
||||
return not _descriptions.is_empty() or not _skill_names.is_empty()
|
||||
return not _descriptions.is_empty() or not _skill_names.is_empty() or not _item_names.is_empty()
|
||||
|
||||
func get_proto_node() -> Object:
|
||||
return _proto_node
|
||||
|
||||
func get_stone_info(stone_vnum: int) -> Dictionary:
|
||||
var name := "魂石 #%d" % stone_vnum
|
||||
var name := ""
|
||||
if _item_names.has(stone_vnum):
|
||||
name = String(_item_names[stone_vnum])
|
||||
var affect := ""
|
||||
var icon_path := ""
|
||||
if _proto_node and _proto_node.has_method("item"):
|
||||
if name.is_empty() and _proto_node and _proto_node.has_method("item"):
|
||||
var sp: Dictionary = _proto_node.item(stone_vnum)
|
||||
if not sp.is_empty():
|
||||
name = String(sp.get("locale_name", sp.get("name", name)))
|
||||
name = String(sp.get("locale_name", sp.get("name", "")))
|
||||
var sa: Array = sp.get("applies", [])
|
||||
if not sa.is_empty():
|
||||
var atype := int(sa[0].get("type", 0))
|
||||
var aval := int(sa[0].get("value", 0))
|
||||
if atype != 0 and aval != 0:
|
||||
affect = format_apply_string(atype, aval)
|
||||
if name.begins_with("魂石 #") and _descriptions.has(stone_vnum):
|
||||
name = String(_descriptions[stone_vnum].get("name", name))
|
||||
elif not affect.is_empty() or (_proto_node and _proto_node.has_method("item")):
|
||||
var sp: Dictionary = _proto_node.item(stone_vnum)
|
||||
if not sp.is_empty():
|
||||
var sa: Array = sp.get("applies", [])
|
||||
if not sa.is_empty():
|
||||
var atype := int(sa[0].get("type", 0))
|
||||
var aval := int(sa[0].get("value", 0))
|
||||
if atype != 0 and aval != 0:
|
||||
affect = format_apply_string(atype, aval)
|
||||
if name.is_empty() and _descriptions.has(stone_vnum):
|
||||
name = String(_descriptions[stone_vnum].get("name", ""))
|
||||
if name.is_empty():
|
||||
name = "魂石 #%d" % stone_vnum
|
||||
return {"name": name, "affect": affect, "icon_path": icon_path}
|
||||
|
||||
func format_apply_string(apply_type: int, value: int) -> String:
|
||||
@@ -317,7 +355,11 @@ func build_model(vnum: int, count: int, proto_data: Dictionary, instance_data: D
|
||||
if vnum <= 0:
|
||||
return {}
|
||||
|
||||
var name := String(proto_data.get("locale_name", proto_data.get("name", "物品 #%d" % vnum)))
|
||||
var name: String = _item_names.get(vnum, "")
|
||||
if name.is_empty():
|
||||
name = String(proto_data.get("locale_name", proto_data.get("name", "")))
|
||||
if name.is_empty() and _descriptions.has(vnum):
|
||||
name = String(_descriptions[vnum].get("name", ""))
|
||||
if name.is_empty():
|
||||
name = "物品 #%d" % vnum
|
||||
var sockets: Array = instance_data.get("sockets", proto_data.get("sockets", []))
|
||||
@@ -530,7 +572,11 @@ func build_model(vnum: int, count: int, proto_data: Dictionary, instance_data: D
|
||||
func format(vnum: int, count: int, proto_data: Dictionary, instance_data: Dictionary = {}) -> String:
|
||||
if vnum <= 0:
|
||||
return ""
|
||||
var name := String(proto_data.get("locale_name", proto_data.get("name", "物品 #%d" % vnum)))
|
||||
var name: String = _item_names.get(vnum, "")
|
||||
if name.is_empty():
|
||||
name = String(proto_data.get("locale_name", proto_data.get("name", "")))
|
||||
if name.is_empty() and _descriptions.has(vnum):
|
||||
name = String(_descriptions[vnum].get("name", ""))
|
||||
if name.is_empty():
|
||||
name = "物品 #%d" % vnum
|
||||
var sockets: Array = instance_data.get("sockets", proto_data.get("sockets", []))
|
||||
|
||||
Reference in New Issue
Block a user