tModLoader 动态更改显示文本(多语言切换)

  1. 参考方法LocalizationLoader.LoadModTranslations(culture)

    如果要更改值,那就 lang.GetText(Key).SetValue(value);

1
2
3
4
5
6
7
var lang = LanguageManager.Instance;
foreach (var mod in ModLoader.Mods) {
	foreach (var (key, value) in LoadTranslations(mod, culture)) {
        //lang.GetText(Key)返的是LocalizatText
		lang.GetText(key).SetValue(value); // can only set the value of existing keys. Cannot register new keys.
	}
}
  1. 参考方法LocalizationLoader.LoadTranslations这里是解析hjson的逻辑,我们无法直接调用此方法,因为GameCulture是当前语言,并不包含繁体中文,并且使用编译时就确定的枚举值,如果使用Unknown,那么只支持一种外部语言
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static List<(string key, string value)> LoadTranslations(Mod mod, GameCulture culture){}

//下面是GameCulture
public enum CultureName
{
	English = 1,
	German = 2,
	Italian = 3,
	French = 4,
	Spanish = 5,
	Russian = 6,
	Chinese = 7,
	Portuguese = 8,
	Polish = 9,
	Unknown = 9999
}
static GameCulture()
{
	_NamedCultures = new Dictionary<CultureName, GameCulture> {
		{ CultureName.English, new GameCulture("en-US", 1) },
		{ CultureName.German, new GameCulture("de-DE", 2) },
		{ CultureName.Italian, new GameCulture("it-IT", 3) },
		{ CultureName.French, new GameCulture("fr-FR", 4) },
		{ CultureName.Spanish, new GameCulture("es-ES", 5) },
		{ CultureName.Russian, new GameCulture("ru-RU", 6) },
		{ CultureName.Chinese, new GameCulture("zh-Hans", 7) },
		{ CultureName.Portuguese, new GameCulture("pt-BR", 8) },
		{ CultureName.Polish, new GameCulture("pl-PL", 9) }
	};

	DefaultCulture = _NamedCultures[CultureName.English];
}
  1. 如果是本就有本地化的模组,只需要保存另外语言的Hjson副本,然后读取内容,使用LocalizationLoader.LoadTranslations中解析的方法

    langLanguageManager实例,其在LanguageManager中存储

    1
    
    public static LanguageManager Instance = new LanguageManager();
    

    SetValue方法是非public的,需要特殊手段

    1
    2
    3
    4
    
    internal void SetValue(string text)
    {
    	Value = text;
    }
    

    但其实LocalizedTextValue是一个

    1
    2
    3
    4
    5
    6
    7
    8
    
    public string Value {
    	get => _value;
    	private set {
    		_value = value;
    		_hasPlurals = null;
    		BoundArgs = null;
    	}
    }
    

    至于GetText是公共的 在LanguageManager

    1
    2
    3
    4
    
    public LocalizedText GetText(string key)
    {
    	return _localizedTexts.TryGetValue(key, out var text) ? text : new LocalizedText(key, key);
    }
    
1
2
3
4
5
6
7
//1. 读取Hjson为文本,这是tModLoader使用的方法
using var stream = mod.File.GetStream(translationFile);
using var streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
string translationFileContents = streamReader.ReadToEnd();
//2. 解析为文本后执行 // Parse HJSON and convert to standard JSON注释下的代码
//此注释在 LocalizationLoader.LoadTranslations(Mod mod, GameCulture culture) 方法中
//结果会返回一个 二元组? 键是key 值是value,该键对应本地化键,然后遍历lang.GetText(Key).SetValue(value);
  1. 另一种方法是不使用lang.GetText(Key).SetValue(value)
    • 获取LanguageManager实例
1
2
3
4
5
6
7
public static object LanguageManager;
public override void Load(){
    Type GameCulture = typeof(LanguageManager);
	//在LanguageManager类下有个Instance字段,是自己的实例 公共的
	FieldInfo Instance = GameCulture.GetField("Instance", BindingFlags.Public | BindingFlags.Static);
	LanguageManager = Instance.GetValue(null);
}
  • 其实在LanguageManager类中有一个字段存放着全部的本地化键和值,只需要获取他的实例,然后得到LocalizedTextValue属性的Set方法,就可以随意取值修改内容

    1
    
    internal readonly Dictionary<string, LocalizedText> _localizedTexts = new Dictionary<string, LocalizedText>();
    
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static Dictionary<string, LocalizedText> LocalizedTexts;
public static MethodInfo LocalizedTextSet;
public override void Load(){
	Type LocalizedText = typeof(LocalizedText);
	//全部字段
	FieldInfo Field = GameCulture.GetField("_localizedTexts", BindingFlags.NonPublic | BindingFlags.Instance);
    //获取实例
	LocalizedTexts = (Dictionary<string, LocalizedText>)Field.GetValue(LanguageManager);
    //Set方法
	LocalizedTextSet = LocalizedText.GetProperty("Value").GetSetMethod(true);
}
  1. 修改
1
2
LocalizedText Text = LocalizedTexts["ItemName.CopperShortsword"];
LocalizedTextSet.Invoke(Text, ["非常好的铜短剑令我脑袋飞转"]);
  1. 对于硬编码,可以使用GetOrRegister注册键然后使用IL钩子,将原本字符串替换为
1
2
Emit(OpCodes.Ldstr, "Key");
Emit(OpCodes.Call, Language.GetTextValue);
使用 Hugo 构建
主题 StackJimmy 设计