添加翻译功能和多语言支持

在 `I18nChatClient.cs` 中实现了与火山方舟API的交互,支持文本翻译的同步和异步方法,并增加了请求重试机制。更新了项目文件以引入必要的依赖包。对UI进行了修改,添加了翻译按钮并重构布局。扩展了视图模型以支持翻译逻辑,更新章节和任务的翻译属性。新增多个模型以支持多语言功能,并在服务类中实现了FTB任务的解析和多语言文件的导入。最后,调整了文件写入编码以确保兼容性。
This commit is contained in:
Ling 2025-06-24 22:56:20 +08:00
parent 6edc4897fa
commit c59bb3e007
12 changed files with 736 additions and 165 deletions

146
AI/I18nChatClient.cs Normal file
View File

@ -0,0 +1,146 @@
using OpenAI;
using OpenAI.Chat;
using System;
using System.ClientModel;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MCI18n.AI
{
internal class I18nChatClient
{
/// <summary>
/// 火山方舟API Key
/// </summary>
private string API_KEY = "API_KEY";
private string API_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3";
private readonly ChatClient _client;
// 使用信号量限制并发请求数
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(2, 3); // 最多2个并发请求可根据API限制调整
// 重试次数和延迟
private const int MaxRetries = 3;
private static readonly TimeSpan InitialRetryDelay = TimeSpan.FromSeconds(1);
public I18nChatClient(string modelName)
{
_client = new ChatClient(modelName, new ApiKeyCredential(API_KEY), new OpenAIClientOptions
{
Endpoint = new Uri(API_ENDPOINT),
NetworkTimeout = TimeSpan.FromSeconds(20d)
});
}
private string SystemPrompt = @"
minecraft相关的语言翻译机器人
Minecraft与社区Mod相关语言的一个
{}
";
private string Prompt = @"
{0}
:
{1}
";
public string Translate(string text, string targetLanguage)
{
try
{
_semaphore.Wait(); // 获取信号量
try
{
var quest = string.Format(Prompt, targetLanguage, text);
var response = _client.CompleteChat(
ChatMessage.CreateSystemMessage(SystemPrompt),
ChatMessage.CreateUserMessage($"{quest}")
);
if (!string.IsNullOrWhiteSpace(response.Value.Content[0].Text))
{
return response.Value.Content[0].Text;
}
else
{
throw new Exception("No response from translation model.");
}
}
finally
{
_semaphore.Release(); // 释放信号量
}
}
catch (Exception ex)
{
throw;
}
}
public async Task<string> TranslateAsync(string text, string targetLanguage)
{
int retries = 0;
TimeSpan delay = InitialRetryDelay;
while (true)
{
try
{
await _semaphore.WaitAsync(); // 异步获取信号量
try
{
var quest = string.Format(Prompt, targetLanguage, text);
var response = await _client.CompleteChatAsync(
ChatMessage.CreateSystemMessage(SystemPrompt),
ChatMessage.CreateUserMessage($"{quest}")
);
if (!string.IsNullOrWhiteSpace(response.Value.Content[0].Text))
{
return response.Value.Content[0].Text;
}
else
{
throw new Exception("No response from translation model.");
}
}
finally
{
_semaphore.Release(); // 释放信号量
}
}
catch (ClientResultException ex) when (ex.Status == 429 && retries < MaxRetries)
{
// 针对429错误特殊处理采用退避策略
retries++;
await Task.Delay(delay);
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2); // 指数退避
continue; // 重试请求
}
catch (Exception ex)
{
throw;
}
break; // 如果成功或者非429错误退出循环
}
// 这里不会被执行到,只是为了满足编译器要求
throw new Exception("Unexpected code path");
}
}
}

View File

@ -18,7 +18,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.AI" Version="9.6.0" />
<PackageReference Include="OpenAI" Version="2.1.0" />
</ItemGroup>
<ItemGroup>

View File

@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCI18n", "MCI18n.csproj", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpNBT", "..\SharpNBT\SharpNBT\SharpNBT.csproj", "{173F7856-B894-5F24-D410-91B18378A44B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "..\ConsoleApp1\ConsoleApp1.csproj", "{2B780307-A7BB-4037-8C96-225544BB6C7B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -21,6 +23,10 @@ Global
{173F7856-B894-5F24-D410-91B18378A44B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{173F7856-B894-5F24-D410-91B18378A44B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{173F7856-B894-5F24-D410-91B18378A44B}.Release|Any CPU.Build.0 = Release|Any CPU
{2B780307-A7BB-4037-8C96-225544BB6C7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B780307-A7BB-4037-8C96-225544BB6C7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B780307-A7BB-4037-8C96-225544BB6C7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B780307-A7BB-4037-8C96-225544BB6C7B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -49,7 +49,9 @@
Command="{Binding SaveQuestCommand}"
Content="打包"
IsEnabled="False" />
<StackPanel Visibility="{}">
</StackPanel>
</StackPanel>
<TabControl Grid.Row="1">
<TabItem Header="任务列表">
@ -73,40 +75,55 @@
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ScrollViewer Grid.Column="1">
<ItemsControl ItemsSource="{Binding SelectedQuests}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox>
<GroupBox.Header>
<StackPanel Orientation="Vertical">
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button
Width="100"
Height="30"
Command="{Binding TranslateTheChapterCommand}"
Content="全部翻译AI"
FontSize="14" />
</StackPanel>
<ScrollViewer Grid.Row="1">
<ItemsControl ItemsSource="{Binding SelectedQuests}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox>
<GroupBox.Header>
<StackPanel Orientation="Vertical">
<TextBox
Background="Transparent"
BorderThickness="0"
FontSize="12"
Foreground="White"
Text="{Binding Title}" />
<TextBox
Background="Transparent"
BorderThickness="0"
FontSize="12"
Foreground="LightGray"
Text="{Binding SubTitle}" />
</StackPanel>
</GroupBox.Header>
<StackPanel>
<TextBlock Text="描述:" />
<TextBox
Background="Transparent"
BorderThickness="0"
FontSize="12"
Foreground="White"
Text="{Binding Title}" />
<TextBox
Background="Transparent"
BorderThickness="0"
FontSize="12"
Foreground="LightGray"
Text="{Binding SubTitle}" />
AcceptsReturn="True"
Text="{Binding Description}"
TextWrapping="Wrap" />
</StackPanel>
</GroupBox.Header>
<StackPanel>
<TextBlock Text="描述:" />
<TextBox
AcceptsReturn="True"
Text="{Binding Description}"
TextWrapping="Wrap" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
</TabItem>
<TabItem Header="战利品列表" />

View File

@ -1,7 +1,9 @@

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MCI18n.AI;
using MCI18n.Models;
using MCI18n.Services;
using MCI18n.Utilities;
using Microsoft.Win32;
using SharpNBT;
@ -25,14 +27,15 @@ namespace MCI18n
private List<QuestLangModel> _langs;
[ObservableProperty]
private ObservableCollection<ChapterModel> _chapters;
private FTBQuestsService _service;
[ObservableProperty]
private ObservableCollection<QuestModel> _selectedQuests;
[ObservableProperty]
private bool _loading = false;
[RelayCommand]
private void LoadQuests()
@ -48,131 +51,16 @@ namespace MCI18n
try
{
var folderPath = openFolderDialog.FolderName;
GlobalContext.MultipleLangSupport = Path.Exists(Path.Combine(folderPath, "quests", "lang"));
if (GlobalContext.MultipleLangSupport)
_service = new FTBQuestsService(folderPath);
var ftbQuests = _service.Parse();
Chapters = [];
foreach (var group in ftbQuests.Groups)
{
var enLangFile = Path.Combine(folderPath, "quests", "lang", "en_us.snbt");
var langData = StringNbt.Parse(File.ReadAllText(enLangFile));
_langs = [];
foreach (var item in langData)
foreach (var chapter in group.Chapters)
{
var langKeys = item.Name.Split('.');
var langItem = new QuestLangModel
{
Type = langKeys[0],
Id = langKeys[1],
DocType = langKeys[2],
};
if (item is StringTag langTag)
{
langItem.Value = langTag.Value;
}
else if(item is ListTag valueList)
{
var descList = new StringBuilder();
foreach (var value in valueList)
{
descList.AppendLine((value as StringTag).Value);
}
langItem.Value = descList.ToString();
}
_langs.Add(langItem);
Chapters.Add(chapter);
}
}
if(_langs != null)
{
var windowTitle = "FTB Quests Editor - " + _langs.FirstOrDefault(it => it.Type == "file" && it.Id == "0000000000000001" && it.DocType == "title").Value;
Application.Current.MainWindow.Title = windowTitle;
}
if (Path.Exists(Path.Combine(folderPath, "quests", "chapters")))
{
var fullQuestsPath = Path.Combine(folderPath, "quests", "chapters");
var questFiles = Directory.GetFiles(fullQuestsPath, "*.snbt", SearchOption.AllDirectories);
_oriQuestsData = new List<CompoundTag>();
_updatedChapters = new List<string>();
Chapters = new ObservableCollection<ChapterModel>();
foreach (var file in questFiles)
{
var questData = StringNbt.Parse(File.ReadAllText(file));
_oriQuestsData.Add(questData);
var chapterId = (questData["id"] as StringTag)?.Value;
var chapterName = (questData["filename"] as StringTag)?.Value ;
var chapterTitle = questData.ContainsKey("title") ? (questData["title"] as StringTag)?.Value : "";
var chapter = new ChapterModel
{
Id = chapterId,
Name = chapterName,
Title = chapterTitle
};
if (GlobalContext.MultipleLangSupport)
{
chapter.Title = GetMutliLang("chapter",chapter.Id, "title");
}
Chapters.Add(chapter);
var quests = questData["quests"];
if(quests is ListTag questList)
{
foreach (var item in questList)
{
if (item is CompoundTag questTag)
{
var questId = (questTag["id"] as StringTag)?.Value;
var questTitle = "";
if (questTag.ContainsKey("title"))
{
questTitle = (questTag["title"] as StringTag)?.Value ?? "";
}
else if (GlobalContext.MultipleLangSupport)
{
questTitle = GetMutliLang("quest", questId, "title");
}
var questSubTitle = "";
if (questTag.ContainsKey("subtitle"))
{
questSubTitle = (questTag["subtitle"] as StringTag)?.Value ?? "";
}
else if (GlobalContext.MultipleLangSupport)
{
questSubTitle = GetMutliLang("quest", questId, "quest_subtitle");
}
var questDescriptions = new StringBuilder();
if (questTag.ContainsKey("description"))
{
if (questTag["description"] is ListTag descriptions)
{
foreach (var desc in descriptions)
{
questDescriptions.AppendLine((desc as StringTag)?.Value);
}
}
}
else if (GlobalContext.MultipleLangSupport)
{
var desc = GetMutliLang("quest", questId, "quest_desc");
questDescriptions.AppendLine(desc);
}
var questModel = new QuestModel
{
Id = questId,
Title = questTitle,
SubTitle = questSubTitle,
Description = questDescriptions.ToString()
};
chapter.Quests.Add(questModel);
}
}
}
}
SelectedQuests = Chapters.First()?.Quests ?? [];
}
else
{
MessageBox.Show("文件夹选择错误!");
}
//var questData = StringNbt.Parse(File.ReadAllText(CurrentFilePath));
//SnbtWriter.SaveToFile(questData, CurrentFilePath + ".r", prettyPrint: true);
}
catch (Exception ex)
{
@ -184,7 +72,7 @@ namespace MCI18n
[RelayCommand]
private void SaveQuest()
{
//TODO Sava ALL (非多语言)
}
[RelayCommand]
@ -206,6 +94,7 @@ namespace MCI18n
{
var filePath = saveFileDialog.FileName;
var langData = new CompoundTag("");
langData.Add(new StringTag("file.0000000000000001.title", Application.Current.MainWindow.Title));
foreach (var chapter in Chapters)
{
langData.Add(new StringTag($"chapter.{chapter.Id}.title", chapter.Title));
@ -214,12 +103,16 @@ namespace MCI18n
foreach (var quest in chapter.Quests)
{
langData.Add(new StringTag($"quest.{quest.Id}.title", quest.Title));
langData.Add(new StringTag($"quest.{quest.Id}.quest_subtitle", quest.SubTitle));
if (!string.IsNullOrWhiteSpace(quest.SubTitle))
{
langData.Add(new StringTag($"quest.{quest.Id}.quest_subtitle", quest.SubTitle));
}
var descLines = quest.Description.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
var descList = new ListTag($"quest.{quest.Id}.quest_desc",TagType.String);
foreach (var line in descLines)
{
descList.Add(new StringTag(null, line));
if(!string.IsNullOrWhiteSpace(line))
descList.Add(new StringTag(null, line));
}
langData.Add(descList);
}
@ -242,6 +135,51 @@ namespace MCI18n
}
[RelayCommand]
private async Task TranslateTheChapter()
{
Loading = true;
try
{
var aiClient = new I18nChatClient("doubao-1-5-pro-32k-250115");
var translationTasks = new List<Task>();
foreach (var item in SelectedQuests)
{
translationTasks.Add(TranslateQuestAsync(item, aiClient));
}
await Task.WhenAll(translationTasks);
}
finally
{
Loading = false;
}
}
private async Task TranslateQuestAsync(QuestModel quest, I18nChatClient aiClient)
{
var titleTask = !string.IsNullOrWhiteSpace(quest.Title)
? aiClient.TranslateAsync(quest.Title, "zh_cn")
: Task.FromResult<string>(quest.Title);
var subtitleTask = !string.IsNullOrWhiteSpace(quest.SubTitle)
? aiClient.TranslateAsync(quest.SubTitle, "zh_cn")
: Task.FromResult<string>(quest.SubTitle);
var descriptionTask = !string.IsNullOrWhiteSpace(quest.Description)
? aiClient.TranslateAsync(quest.Description, "zh_cn")
: Task.FromResult<string>(quest.Description);
// 等待所有翻译任务完成
await Task.WhenAll(titleTask, subtitleTask, descriptionTask);
// 更新属性
quest.Title = await titleTask;
quest.SubTitle = await subtitleTask;
quest.Description = await descriptionTask;
}
internal void ChapterSelectionChanged(ChapterModel selectedChapter)
{
if (selectedChapter != null && selectedChapter.Quests != null)
@ -253,10 +191,6 @@ namespace MCI18n
SelectedQuests = [];
}
}
private string GetMutliLang(string type,string id,string docType)
{
return _langs?.FirstOrDefault(it => it.Type == type && it.Id == id && it.DocType == docType)?.Value ?? "";
}
}
}
}

View File

@ -0,0 +1,25 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Documents;
namespace MCI18n.Models
{
public partial class ChapterGroupModel : ObservableObject
{
[ObservableProperty]
private string _id;
[ObservableProperty]
private string _name;
[ObservableProperty]
private ObservableCollection<ChapterModel> _chapters = [];
}
}

View File

@ -12,11 +12,14 @@ namespace MCI18n.Models
{
[ObservableProperty]
private string _id;
[ObservableProperty]
private string _groupId;
[ObservableProperty]
private string _name;
[ObservableProperty]
private string _title;
[ObservableProperty]
private ObservableCollection<QuestModel> _quests = new ObservableCollection<QuestModel>();
private ObservableCollection<QuestModel> _quests = [];
}
}

22
Models/FTBQuestsModel.cs Normal file
View File

@ -0,0 +1,22 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MCI18n.Models
{
public partial class FTBQuestsModel : ObservableObject
{
[ObservableProperty]
private string _id;
[ObservableProperty]
private string _title;
[ObservableProperty]
private ObservableCollection<ChapterGroupModel> _groups = [];
}
}

View File

@ -15,7 +15,15 @@ namespace MCI18n.Models
private string _id;
[ObservableProperty]
private string _docType;
/// <summary>
/// 多语言内容
/// </summary>
[ObservableProperty]
private string _value;
/// <summary>
/// 当导入多语言时,保留原文
/// </summary>
[ObservableProperty]
private string _originalValue;
}
}

View File

@ -0,0 +1,395 @@
using MCI18n.Models;
using MCI18n.Utilities;
using SharpNBT;
using SharpNBT.SNBT;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace MCI18n.Services
{
public class FTBQuestsService
{
private string _ftbQuestsPath;
private bool _multiLanguageSupportEnabled = false;
private FTBQuestsModel FTBQuests;
private List<QuestLangModel> _langs = [];
private string OriginalLang = "en_us";
/// <summary>
/// 初始化
/// </summary>
/// <param name="ftbQuestsPath">eg: ftbquests/quests</param>
public FTBQuestsService(string ftbQuestsPath)
{
_ftbQuestsPath = ftbQuestsPath;
CheckMultiLanguageSupport();
FTBQuests = new FTBQuestsModel();
}
/// <summary>
/// 设置源语言代码
/// </summary>
/// <param name="langCode"></param>
/// <returns></returns>
public FTBQuestsService SetOriginalLang(string langCode)
{
OriginalLang = langCode;
return this;
}
/// <summary>
/// 检查是否支持多语言
/// </summary>
public void CheckMultiLanguageSupport()
{
_multiLanguageSupportEnabled = Path.Exists(Path.Combine(_ftbQuestsPath, Consts.FTBQuestsLangPath));
GlobalContext.MultipleLangSupport = _multiLanguageSupportEnabled;
}
public FTBQuestsModel Parse()
{
ParseData();
ParseGroup();
ParseDefaultLang();
ParseFTBQuests();
//if (_multiLanguageSupportEnabled)
//{
// ParseExistsLang(OriginalLang);
//}
return FTBQuests;
}
public void ParseData()
{
var dataFile = Path.Combine(_ftbQuestsPath, "data.snbt");
if (File.Exists(dataFile))
{
var dataContent = StringNbt.Parse(File.ReadAllText(dataFile));
if (dataContent.ContainsKey("ftbquests"))
{
var ftbQuestsData = dataContent["ftbquests"] as CompoundTag;
if (ftbQuestsData != null)
{
FTBQuests.Id = ftbQuestsData["id"] as StringTag ?? "";
if (!_multiLanguageSupportEnabled)
{
FTBQuests.Title = ftbQuestsData.ContainsKey("title") ? (ftbQuestsData["title"] as StringTag)?.Value ?? "" : "";
}
else
{
FTBQuests.Title = GetMutliLang("0000000000000001", "file");
}
}
}
}
}
public void ParseGroup()
{
var groupFile = Path.Combine(_ftbQuestsPath,"quests", "chapter_groups.snbt");
if (File.Exists(groupFile))
{
var groupData = StringNbt.Parse(File.ReadAllText(groupFile));
var groups = new ObservableCollection<ChapterGroupModel>();
foreach (var item in groupData)
{
if (item is ListTag groupTags)
{
foreach(var group in groupTags)
{
if(group is CompoundTag groupTag)
{
var groupModel = new ChapterGroupModel
{
Id = groupTag["id"] as StringTag,
Chapters = new ObservableCollection<ChapterModel>()
};
groups.Add(groupModel);
}
}
}
}
FTBQuests.Groups = groups;
}
}
public void ParseDefaultLang()
{
if (_multiLanguageSupportEnabled && ExistsMultiLangFiles())
{
var enLangFile = Path.Combine(_ftbQuestsPath, Consts.FTBQuestsLangPath, "en_us.snbt");
var langData = StringNbt.Parse(File.ReadAllText(enLangFile));
foreach (var item in langData)
{
if (item == null) continue;
var langItem = ParseLangData(item);
_langs.Add(langItem);
}
}
}
public void ParseFTBQuests()
{
if (Path.Exists(Path.Combine(_ftbQuestsPath, "quests", "chapters")))
{
var fullQuestsPath = Path.Combine(_ftbQuestsPath, "quests", "chapters");
var questFiles = Directory.GetFiles(fullQuestsPath, "*.snbt", SearchOption.AllDirectories);
var chapters = new ObservableCollection<ChapterModel>();
foreach (var file in questFiles)
{
var questData = StringNbt.Parse(File.ReadAllText(file));
//_oriQuestsData.Add(questData);
var chapter = ParseChapter(questData);
chapters.Add(chapter);
var quests = questData["quests"];
if (quests is ListTag questList)
{
foreach (var item in questList)
{
if (item is CompoundTag questTag)
{
var questModel = ParseQuest(questTag);
chapter.Quests.Add(questModel);
}
}
}
}
foreach (var chapter in chapters)
{
foreach (var group in FTBQuests.Groups)
{
if(chapter.GroupId == group.Id)
{
group.Chapters.Add(chapter);
}
}
}
}
else
{
MessageBox.Show("文件夹选择错误!");
}
}
/// <summary>
/// 解析多语言数据
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public QuestLangModel ParseLangData(Tag item)
{
var langKeys = item!.Name.Split('.');
var langItem = new QuestLangModel
{
Type = langKeys[0],
Id = langKeys[1],
DocType = langKeys[2],
};
if (item is StringTag langTag)
{
langItem.Value = langTag.Value;
}
else if (item is ListTag valueList)
{
var descList = new StringBuilder();
foreach (var value in valueList)
{
descList.AppendLine((value as StringTag).Value);
}
langItem.Value = descList.ToString();
}
return langItem;
}
/// <summary>
/// 解析章节数据
/// </summary>
/// <param name="questData"></param>
/// <returns></returns>
public ChapterModel ParseChapter(CompoundTag questData)
{
var chapterId = (questData["id"] as StringTag)?.Value ?? "";
var chapterName = (questData["filename"] as StringTag)?.Value ?? "";
var groupId = (questData["group"] as StringTag)?.Value ?? "";
var chapterTitle = questData.ContainsKey("title")
? (questData["title"] as StringTag)?.Value ?? ""
: "";
var chapter = new ChapterModel
{
Id = chapterId,
GroupId = groupId,
Name = chapterName,
Title = chapterTitle
};
if (_multiLanguageSupportEnabled)
{
chapter.Title = GetMutliLang(chapter.Id,"chapter", "title");
}
return chapter;
}
/// <summary>
/// 解析任务数据
/// </summary>
/// <param name="questTag"></param>
/// <returns></returns>
public QuestModel ParseQuest(CompoundTag questTag)
{
var questId = (questTag["id"] as StringTag)?.Value ?? "";
var questTitle = "";
if (questTag.ContainsKey("title"))
{
questTitle = (questTag["title"] as StringTag)?.Value ?? "";
}
else if (_multiLanguageSupportEnabled)
{
questTitle = GetMutliLang(questId, "quest", "title");
}
var questSubTitle = "";
if (questTag.ContainsKey("subtitle"))
{
questSubTitle = (questTag["subtitle"] as StringTag)?.Value ?? "";
}
else if (_multiLanguageSupportEnabled)
{
questSubTitle = GetMutliLang(questId, "quest", "quest_subtitle");
}
var questDescriptions = new StringBuilder();
if (questTag.ContainsKey("description"))
{
if (questTag["description"] is ListTag descriptions)
{
foreach (var desc in descriptions)
{
questDescriptions.AppendLine((desc as StringTag)?.Value);
}
}
}
else if (_multiLanguageSupportEnabled)
{
var desc = GetMutliLang(questId, "quest", "quest_desc");
questDescriptions.AppendLine(desc);
}
var questModel = new QuestModel
{
Id = questId,
Title = questTitle,
SubTitle = questSubTitle,
Description = questDescriptions.ToString(),
Tasks = []
};
if (questTag.ContainsKey("tasks"))
{
var tasks = questTag["tasks"] as ListTag;
if (tasks != null)
{
foreach (var task in tasks)
{
if (task is CompoundTag taskTag)
{
var taskModel = new TaskModel
{
Id = taskTag["id"] as StringTag ?? "",
};
if (taskTag.ContainsKey("title"))
{
taskModel.Title = (taskTag["title"] as StringTag)?.Value ?? "";
}
else if (_multiLanguageSupportEnabled)
{
taskModel.Title = GetMutliLang(taskModel.Id, "task", "title");
}
questModel.Tasks.Add(taskModel);
}
}
}
}
return questModel;
}
/// <summary>
/// 导入已有的多语言文件
/// </summary>
/// <param name="targetLangCode"></param>
public void ParseExistsLang(string targetLangCode)
{
if (GlobalContext.MultipleLangSupport)
{
var langFile = Path.Combine(_ftbQuestsPath, Consts.FTBQuestsLangPath, $"{targetLangCode}.snbt");
if (File.Exists(langFile))
{
var langData = StringNbt.Parse(File.ReadAllText(langFile));
foreach (var item in langData)
{
if (item == null) continue;
var langItem = ParseLangData(item);
UpdateLang(langItem.Id, langItem.Value, langItem.Type, langItem.DocType);
}
}
}
}
/// <summary>
/// 获取多语言文本
/// </summary>
/// <param name="id">0000000000000001</param>
/// <param name="type">file</param>
/// <param name="docType">title</param>
/// <returns></returns>
public string GetMutliLang(string id, string type = "quests", string docType = "title")
{
return _langs?.FirstOrDefault(it => it.Type == type && it.Id == id && it.DocType == docType)?.Value ?? "";
}
/// <summary>
/// 更新多语言文本
/// </summary>
/// <param name="id"></param>
/// <param name="value"></param>
/// <param name="type"></param>
/// <param name="docType"></param>
public void UpdateLang(string id, string value, string type = "quests", string docType = "title")
{
var langItem = _langs.FirstOrDefault(it => it.Type == type && it.Id == id && it.DocType == docType);
if (langItem != null)
{
langItem.OriginalValue = langItem.Value;
langItem.Value = value;
}
else
{
_langs.Add(new QuestLangModel
{
Type = type,
Id = id,
DocType = docType,
Value = value
});
}
}
/// <summary>
/// 是否存在多语言文件夹
/// </summary>
/// <returns></returns>
public bool ExistsMultiLangFiles()
{
return Path.Exists(Path.Combine(_ftbQuestsPath, Consts.FTBQuestsLangPath));
}
}
}

13
Utilities/Consts.cs Normal file
View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MCI18n.Utilities
{
public class Consts
{
public const string FTBQuestsLangPath = "quests/lang";
}
}

View File

@ -30,7 +30,7 @@ namespace MCI18n.Utilities
try
{
string snbt = ConvertToSnbt(tag, prettyPrint);
File.WriteAllText(filePath, snbt, Encoding.UTF8);
File.WriteAllText(filePath, snbt, new UTF8Encoding(false));
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or DirectoryNotFoundException)
{