feat: 维修科目步骤描述多语言支持 + LocalizeOperateMsg修复

- 创建维修案例1/2的英/法/俄翻译版XML文件
- TeacherMainPanel: 根据语言选择对应翻译版XML下发
- StudentSub3Panel: 所有InjectOperateMsg改为InjectOperateMsgLocalized
- StudentOperateRecorder: 新增InjectOperateMsgLocalizedWithTime方法
- LocalizationManager: 修复LocalizeOperateMsg双重格式化bug
- LocalizationManager: 新增维修步骤模板/拆卸/安装翻译条目
This commit is contained in:
ayuan9957 2026-07-16 11:36:18 +08:00
parent bf12e02276
commit a9e03f6ac0
19 changed files with 24338 additions and 123 deletions

View File

@ -6,5 +6,5 @@
"reloading": false,
"reason": "ready",
"seq": 1,
"last_heartbeat": "2026-07-16T01:59:11.3600431Z"
"last_heartbeat": "2026-07-16T03:36:17.6711651Z"
}

File diff suppressed because it is too large Load Diff

View File

@ -330,7 +330,15 @@ public class LocalizationManager : MonoBehaviour
{ "按照15km/h速度计算转运时间", new[] { "按照15km/h速度计算转运时间", "Transport time calculated at 15km/h:", "Temps de transport calculé à 15km/h :", "Время транспортировки рассчитано при 15км/ч:" } },
{ "修理厂转运总用时:{0}时{1}分{2}秒", new[] { "修理厂转运总用时:{0}时{1}分{2}秒", "Total repair site transport time: {0}h {1}m {2}s", "Temps total de transport du site de réparation : {0}h {1}m {2}s", "Общее время транспортировки ремонтного пункта: {0}ч {1}мин {2}с" } },
{ "{0}距离{1}:{2}米,预计转运用时:{3}秒", new[] { "{0}距离{1}:{2}米,预计转运用时:{3}秒", "{0} to {1}: {2}m, estimated transport time: {3}s", "{0} à {1} : {2}m, temps de transport estimé : {3}s", "{0} до {1}: {2}м, расчётное время транспортировки: {3}с" } },
{ "{0}增加车型:{1}\u201C等\u201D{2}种,扣{3}分", new[] { "{0}增加车型:{1}\u201C等\u201D{2}种,扣{3}分", "{0} added vehicle types: {1} etc. {2} types, deduct {3} points", "{0} types de véhicules ajoutés : {1} etc. {2} types, déduction de {3} points", "{0} добавлены типы машин: {1} и др. {2} типов, вычет {3} баллов" } }
{ "{0}增加车型:{1}\u201C等\u201D{2}种,扣{3}分", new[] { "{0}增加车型:{1}\u201C等\u201D{2}种,扣{3}分", "{0} added vehicle types: {1} etc. {2} types, deduct {3} points", "{0} types de véhicules ajoutés : {1} etc. {2} types, déduction de {3} points", "{0} добавлены типы машин: {1} и др. {2} типов, вычет {3} баллов" } },
{ "{0}环节规划完成,步骤如下:", new[] { "{0}环节规划完成,步骤如下:", "{0} phase planning complete, steps:", "{0} phase de planification terminée, étapes :", "{0} этап планирования завершён, шаги:" } },
{ "{0}、{1}", new[] { "{0}、{1}", "{0}. {1}", "{0}. {1}", "{0}. {1}" } },
{ "{0}回答错误!", new[] { "{0}回答错误!", "{0} answered incorrectly!", "{0} réponse incorrecte !", "{0} ответ неверный!" } },
{ "完成{0}流程虚拟操作。", new[] { "完成{0}流程虚拟操作。", "Completed {0} virtual operation.", "Opération virtuelle {0} terminée.", "Завершена виртуальная операция {0}." } },
{ "{0}{1}完成。", new[] { "{0}{1}完成。", "{0} {1} complete.", "{0} {1} terminé.", "{0} {1} завершено." } },
{ "{0} 之前需要先完成 {1}", new[] { "{0} 之前需要先完成 {1}", "{0} requires completing first: {1}", "{0} nécessite d'abord terminer {1}", "{0} сначала нужно выполнить {1}" } },
{ "拆卸", new[] { "拆卸", "Disassembly", "Démontage", "Разборка" } },
{ "安装", new[] { "安装", "Installation", "Installation", "Установка" } }
};
[SerializeField] private int language;
@ -479,12 +487,29 @@ public class LocalizationManager : MonoBehaviour
if (string.IsNullOrEmpty(stored) || stored[0] != '\x01') return stored;
string[] parts = stored.Substring(1).Split('\x02');
string key = parts[0];
if (key == "{0}:")
{
// Time-prefixed message: parts[1]=time, parts[2]=key, parts[3..]=args
if (parts.Length >= 3)
{
string time = parts[1];
string actualKey = parts[2];
if (parts.Length > 3)
{
object[] args = new object[parts.Length - 3];
for (int i = 0; i < args.Length; i++)
args[i] = parts[i + 3];
return time + ":" + string.Format(Get(actualKey), instance.TranslateArgs(args));
}
return time + ":" + Get(actualKey);
}
}
if (parts.Length > 1)
{
object[] args = new object[parts.Length - 1];
for (int i = 0; i < args.Length; i++)
args[i] = parts[i + 1];
return string.Format(Format(key, args), instance.TranslateArgs(args));
return string.Format(Get(key), instance.TranslateArgs(args));
}
return Get(key);
}

View File

@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DevelopEngine;
@ -70,6 +70,308 @@ public class StudentOperateRecorder : Singleton<StudentOperateRecorder>
resultMsg.errors.Add(stored);
}
/// <summary>
/// 记录一条带时间前缀的本地化操作消息
/// </summary>
public void InjectOperateMsgLocalizedWithTime(string key, params object[] args)
{
if (resultMsg.errors == null)
{
Debug.LogError("未设置科目进行初始化");
return;
}
string stored = "\x01{0}:\x02" + GetTime() + "\x02" + key;
if (args != null)
{
for (int i = 0; i < args.Length; i++)
stored += "\x02" + (args[i] == null ? "" : args[i].ToString());
}
resultMsg.errors.Add(stored);
}
/// <summary>
/// 记录每个扣分项
/// </summary>

View File

@ -68,12 +68,136 @@ public class StudentSub3Panel : BasePanel
}
//当前环节规划完成 记录规划的步骤
private void PlanningStepOver(List<StepData> stepDatas)
{
StudentOperateRecorder.Instance.InjectOperateMsg($"{fixLinks[linkIndex].fixLinkEnum}环节规划完成,步骤如下:");//
StudentOperateRecorder.Instance.InjectOperateMsgLocalized("{0}环节规划完成,步骤如下:", fixLinks[linkIndex].fixLinkEnum.ToString());
for (int i = 0; i < stepDatas.Count; i++)
{
StudentOperateRecorder.Instance.InjectOperateMsg($"{i}、{stepDatas[i].describe}");
StudentOperateRecorder.Instance.InjectOperateMsgLocalized("{0}、{1}", i, stepDatas[i].describe);
}
if (fixLinks[linkIndex].questions != null && fixLinks[linkIndex].questions.Count > 0)//当前环节存在需要回答的问题
{
@ -92,7 +216,7 @@ public class StudentSub3Panel : BasePanel
{
if (!isRight)//回答错误时 记录 正确时不做处理
{
StudentOperateRecorder.Instance.InjectOperateMsg($"{fixLinks[linkIndex].questions[currentQuestionIdex].question}回答错误!");
StudentOperateRecorder.Instance.InjectOperateMsgLocalized("{0}回答错误!", fixLinks[linkIndex].questions[currentQuestionIdex].question);
}
if (currentQuestionIdex < fixLinks[linkIndex].questions.Count - 1)//当前修理环节存在未回答的问题
@ -128,7 +252,7 @@ public class StudentSub3Panel : BasePanel
private void VROperatorProcessOver(ProcessEnum processEnum)
{
ProcessManager.Instance.StepOverEvent -= VRStepOverEvent;
StudentOperateRecorder.Instance.InjectOperateMsg($"完成{processEnum.ToString()}流程虚拟操作。",true);
StudentOperateRecorder.Instance.InjectOperateMsgLocalizedWithTime("完成{0}流程虚拟操作。", processEnum.ToString());
if (linkIndex < fixLinks.Count - 1)//存在未完成的环节
{
linkIndex++;
@ -155,15 +279,295 @@ public class StudentSub3Panel : BasePanel
}
private void VRStepOverEvent(string arg0)
{
if (StepName.StepNameDic.ContainsKey(arg0))//步骤名称存在补充数据
{
if (!string.IsNullOrEmpty(StepName.StepNameDic[arg0]))//补充数据不为空时
StudentOperateRecorder.Instance.InjectOperateMsg($"{StepName.StepNameDic[arg0]}{fixLinks[linkIndex].fixLinkEnum}完成。", true);
StudentOperateRecorder.Instance.InjectOperateMsgLocalizedWithTime("{0}{1}完成。", StepName.StepNameDic[arg0], fixLinks[linkIndex].fixLinkEnum.ToString());
return;//为空则代表该步骤不做记录
}
//步骤名称不存在补充数据 按照原有规则记录
StudentOperateRecorder.Instance.InjectOperateMsg($"{arg0}{fixLinks[linkIndex].fixLinkEnum}完成。", true);
StudentOperateRecorder.Instance.InjectOperateMsgLocalizedWithTime("{0}{1}完成。", arg0, fixLinks[linkIndex].fixLinkEnum.ToString());
}
}

View File

@ -487,7 +487,17 @@ public class TeacherMainPanel : BasePanel
netSub3Msg.caseName = $"案例{dropdownRepairSubject.value}";
netSub3Msg.weather = dropdownWeather.captionText.text;
netSub3Msg.groupLearn = netGroupLearn;
netSub3Msg.fixLinks = (List<FixLink>)XmlHelper.DeserializeObject(Application.streamingAssetsPath + $"/XML/装备维修/维修{netSub3Msg.caseName}.xml", typeof(List<FixLink>));//根据科目获取当前维修案例配置文件
string langSuffix = "";
switch ((int)LocalizationManager.Language)
{
case 1: langSuffix = "_en"; break;
case 2: langSuffix = "_fr"; break;
case 3: langSuffix = "_ru"; break;
}
string xmlPath = Application.streamingAssetsPath + $"/XML/装备维修/维修{netSub3Msg.caseName}{langSuffix}.xml";
if (!System.IO.File.Exists(xmlPath))
xmlPath = Application.streamingAssetsPath + $"/XML/装备维修/维修{netSub3Msg.caseName}.xml";
netSub3Msg.fixLinks = (List<FixLink>)XmlHelper.DeserializeObject(xmlPath, typeof(List<FixLink>));//根据科目获取当前维修案例配置文件
TCPManager.Instance.SendSubject(item.ip, subNameEnum, netSub3Msg);
break;
default:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c94bc70dfc5c7624f8da1785d27597dd
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 893e4d752d54942429a2204e33d8e93d
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0f39a3b7f8cbec2488861ae2a37abbe9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: de1ef46e52a09bb43a04f6999f7967c9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2e20b27f51e38b44bbf20bcdcae9bd37
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f479011881a6d294686865bff42a8508
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: