添加翻译功能和多语言支持
在 `I18nChatClient.cs` 中实现了与火山方舟API的交互,支持文本翻译的同步和异步方法,并增加了请求重试机制。更新了项目文件以引入必要的依赖包。对UI进行了修改,添加了翻译按钮并重构布局。扩展了视图模型以支持翻译逻辑,更新章节和任务的翻译属性。新增多个模型以支持多语言功能,并在服务类中实现了FTB任务的解析和多语言文件的导入。最后,调整了文件写入编码以确保兼容性。
This commit is contained in:
parent
6edc4897fa
commit
c59bb3e007
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCI18n", "MCI18n.csproj", "
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpNBT", "..\SharpNBT\SharpNBT\SharpNBT.csproj", "{173F7856-B894-5F24-D410-91B18378A44B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpNBT", "..\SharpNBT\SharpNBT\SharpNBT.csproj", "{173F7856-B894-5F24-D410-91B18378A44B}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "..\ConsoleApp1\ConsoleApp1.csproj", "{2B780307-A7BB-4037-8C96-225544BB6C7B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{173F7856-B894-5F24-D410-91B18378A44B}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@
|
||||||
Command="{Binding SaveQuestCommand}"
|
Command="{Binding SaveQuestCommand}"
|
||||||
Content="打包"
|
Content="打包"
|
||||||
IsEnabled="False" />
|
IsEnabled="False" />
|
||||||
|
<StackPanel Visibility="{}">
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TabControl Grid.Row="1">
|
<TabControl Grid.Row="1">
|
||||||
<TabItem Header="任务列表">
|
<TabItem Header="任务列表">
|
||||||
|
|
@ -73,40 +75,55 @@
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
<ScrollViewer Grid.Column="1">
|
<Grid Grid.Column="1">
|
||||||
<ItemsControl ItemsSource="{Binding SelectedQuests}">
|
<Grid.RowDefinitions>
|
||||||
<ItemsControl.ItemTemplate>
|
<RowDefinition Height="Auto" />
|
||||||
<DataTemplate>
|
<RowDefinition Height="*" />
|
||||||
<GroupBox>
|
|
||||||
<GroupBox.Header>
|
|
||||||
<StackPanel Orientation="Vertical">
|
|
||||||
|
|
||||||
|
</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
|
<TextBox
|
||||||
Background="Transparent"
|
AcceptsReturn="True"
|
||||||
BorderThickness="0"
|
Text="{Binding Description}"
|
||||||
FontSize="12"
|
TextWrapping="Wrap" />
|
||||||
Foreground="White"
|
|
||||||
Text="{Binding Title}" />
|
|
||||||
<TextBox
|
|
||||||
Background="Transparent"
|
|
||||||
BorderThickness="0"
|
|
||||||
FontSize="12"
|
|
||||||
Foreground="LightGray"
|
|
||||||
Text="{Binding SubTitle}" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox.Header>
|
</GroupBox>
|
||||||
<StackPanel>
|
</DataTemplate>
|
||||||
<TextBlock Text="描述:" />
|
</ItemsControl.ItemTemplate>
|
||||||
<TextBox
|
</ItemsControl>
|
||||||
AcceptsReturn="True"
|
</ScrollViewer>
|
||||||
Text="{Binding Description}"
|
</Grid>
|
||||||
TextWrapping="Wrap" />
|
|
||||||
</StackPanel>
|
|
||||||
</GroupBox>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</ScrollViewer>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="战利品列表" />
|
<TabItem Header="战利品列表" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using MCI18n.AI;
|
||||||
using MCI18n.Models;
|
using MCI18n.Models;
|
||||||
|
using MCI18n.Services;
|
||||||
using MCI18n.Utilities;
|
using MCI18n.Utilities;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using SharpNBT;
|
using SharpNBT;
|
||||||
|
|
@ -26,7 +28,7 @@ namespace MCI18n
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<ChapterModel> _chapters;
|
private ObservableCollection<ChapterModel> _chapters;
|
||||||
|
|
||||||
|
private FTBQuestsService _service;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<QuestModel> _selectedQuests;
|
private ObservableCollection<QuestModel> _selectedQuests;
|
||||||
|
|
@ -34,6 +36,7 @@ namespace MCI18n
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _loading = false;
|
private bool _loading = false;
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void LoadQuests()
|
private void LoadQuests()
|
||||||
{
|
{
|
||||||
|
|
@ -48,131 +51,16 @@ namespace MCI18n
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var folderPath = openFolderDialog.FolderName;
|
var folderPath = openFolderDialog.FolderName;
|
||||||
GlobalContext.MultipleLangSupport = Path.Exists(Path.Combine(folderPath, "quests", "lang"));
|
_service = new FTBQuestsService(folderPath);
|
||||||
if (GlobalContext.MultipleLangSupport)
|
var ftbQuests = _service.Parse();
|
||||||
|
Chapters = [];
|
||||||
|
foreach (var group in ftbQuests.Groups)
|
||||||
{
|
{
|
||||||
var enLangFile = Path.Combine(folderPath, "quests", "lang", "en_us.snbt");
|
foreach (var chapter in group.Chapters)
|
||||||
var langData = StringNbt.Parse(File.ReadAllText(enLangFile));
|
|
||||||
_langs = [];
|
|
||||||
foreach (var item in langData)
|
|
||||||
{
|
{
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -184,7 +72,7 @@ namespace MCI18n
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void SaveQuest()
|
private void SaveQuest()
|
||||||
{
|
{
|
||||||
|
//TODO Sava ALL (非多语言)
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -206,6 +94,7 @@ namespace MCI18n
|
||||||
{
|
{
|
||||||
var filePath = saveFileDialog.FileName;
|
var filePath = saveFileDialog.FileName;
|
||||||
var langData = new CompoundTag("");
|
var langData = new CompoundTag("");
|
||||||
|
langData.Add(new StringTag("file.0000000000000001.title", Application.Current.MainWindow.Title));
|
||||||
foreach (var chapter in Chapters)
|
foreach (var chapter in Chapters)
|
||||||
{
|
{
|
||||||
langData.Add(new StringTag($"chapter.{chapter.Id}.title", chapter.Title));
|
langData.Add(new StringTag($"chapter.{chapter.Id}.title", chapter.Title));
|
||||||
|
|
@ -214,12 +103,16 @@ namespace MCI18n
|
||||||
foreach (var quest in chapter.Quests)
|
foreach (var quest in chapter.Quests)
|
||||||
{
|
{
|
||||||
langData.Add(new StringTag($"quest.{quest.Id}.title", quest.Title));
|
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 descLines = quest.Description.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||||
var descList = new ListTag($"quest.{quest.Id}.quest_desc",TagType.String);
|
var descList = new ListTag($"quest.{quest.Id}.quest_desc",TagType.String);
|
||||||
foreach (var line in descLines)
|
foreach (var line in descLines)
|
||||||
{
|
{
|
||||||
descList.Add(new StringTag(null, line));
|
if(!string.IsNullOrWhiteSpace(line))
|
||||||
|
descList.Add(new StringTag(null, line));
|
||||||
}
|
}
|
||||||
langData.Add(descList);
|
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)
|
internal void ChapterSelectionChanged(ChapterModel selectedChapter)
|
||||||
{
|
{
|
||||||
if (selectedChapter != null && selectedChapter.Quests != null)
|
if (selectedChapter != null && selectedChapter.Quests != null)
|
||||||
|
|
@ -254,9 +192,5 @@ namespace MCI18n
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
private string GetMutliLang(string type,string id,string docType)
|
|
||||||
{
|
|
||||||
return _langs?.FirstOrDefault(it => it.Type == type && it.Id == id && it.DocType == docType)?.Value ?? "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,11 +12,14 @@ namespace MCI18n.Models
|
||||||
{
|
{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _id;
|
private string _id;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _groupId;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _name;
|
private string _name;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _title;
|
private string _title;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<QuestModel> _quests = new ObservableCollection<QuestModel>();
|
private ObservableCollection<QuestModel> _quests = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,15 @@ namespace MCI18n.Models
|
||||||
private string _id;
|
private string _id;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _docType;
|
private string _docType;
|
||||||
|
/// <summary>
|
||||||
|
/// 多语言内容
|
||||||
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _value;
|
private string _value;
|
||||||
|
/// <summary>
|
||||||
|
/// 当导入多语言时,保留原文
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _originalValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ namespace MCI18n.Utilities
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string snbt = ConvertToSnbt(tag, prettyPrint);
|
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)
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue