# Locale —— EterLocale 字符串表(`locale/locale//*.txt`,KEY\tVALUE,UTF-8)。 # Godot 原生处理 UTF-8 + 拉丁/西里尔/希腊/阿拉伯;CJK 需外挂字体(apply_font)。 # 用 preload("res://locale.gd").new();setup() 后 t("KEY", [args]). extends RefCounted var _lang := "zh" var _tab := {} # KEY -> value var _item_names := {} # int vnum -> String name var _mob_names := {} # int vnum -> String name var _base := "" 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): return var fa := FileAccess.open(path, FileAccess.READ) if fa == null: return while not fa.eof_reached(): var line := fa.get_line() if line.is_empty() or line.begins_with("#"): continue var tab := line.find("\t") if tab < 0: continue var key := line.substr(0, tab).strip_edges() var val := line.substr(tab + 1).strip_edges() if not key.is_empty(): _tab[key] = val func lang() -> String: return _lang func has(key: String) -> bool: return _tab.has(key) # t("SELECT_LEVEL") -> "Level";t("AFF_LOVE_POINT", [42]) -> "Love points: 42%" # 缺失时返回 ""(开发期一眼可见)。 func t(key: String, args: Array = []) -> String: if not _tab.has(key): return "<" + key + ">" var s: String = _tab[key] if args.is_empty(): return s return s % args if args.size() > 1 else (s % args[0]) # 给一个 Control / Window 挂 CJK(或其它)字体。ttf_path 为空则不动。 func apply_font(node: Control, ttf_path: String, size := 0) -> void: if ttf_path.is_empty() or not FileAccess.file_exists(ttf_path): return var f := FontFile.new() f.load_dynamic_font(ttf_path) node.add_theme_font_override("font", f) if size > 0: node.add_theme_font_size_override("font_size", size)