1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using System.Xml.Linq;
6 
7 namespace OpenBveApi.Interface
8 {
9 	public static partial class Translations
10 	{
11 		private class Language
12 		{
13 			/// <summary>The interface strings for this language</summary>
14 			internal readonly InterfaceString[] InterfaceStrings;
15 			/// <summary>The command information strings for this language</summary>
16 			internal readonly CommandInfo[] myCommandInfos;
17 			/// <summary>The key information strings for this language</summary>
18 			internal readonly KeyInfo[] KeyInfos;
19 			/// <summary>The quick-reference strings for this language</summary>
20 			internal readonly InterfaceQuickReference myQuickReferences;
21 			/// <summary>Returns the number of translated strings contained in the language</summary>
22 			internal int InterfaceStringCount => InterfaceStrings.Length;
23 
24 			/// <summary>The language name</summary>
25 			private readonly string Name;
26 			/// <summary>The language flag</summary>
27 			internal readonly string Flag;
28 			/// <summary>The language code</summary>
29 			internal readonly string LanguageCode;
30 			/// <summary>The language codes on which to fall-back if a string is not found in this language(In order from best to worst)</summary>
31 			/// en-US should always be present in this list
32 			internal readonly List<string> FallbackCodes;
33 
34 			private class XliffFile
35 			{
36 				internal class Unit
37 				{
38 					internal readonly string Id;
39 					internal readonly string Value;
40 
Unit(XNamespace xmlns, XElement unit, string languageCode)41 					internal Unit(XNamespace xmlns, XElement unit, string languageCode)
42 					{
43 						XElement source = unit.Element(xmlns + "source");
44 						XElement target = unit.Element(xmlns + "target");
45 
46 						Id = (string)unit.Attribute("id");
47 
48 						if (target == null && languageCode != "en-US")
49 						{
50 							Value = ((string)source).Replace("\\r\\n", Environment.NewLine).Replace("\\x20", " ");
51 							return;
52 						}
53 
54 						Value = (languageCode != "en-US" ? (string)target : (string)source).Replace("\\r\\n", Environment.NewLine).Replace("\\x20", " ");
55 						if (string.IsNullOrEmpty(Value))
56 						{
57 							//if target is empty / null, let's use the untranslated value https://github.com/leezer3/OpenBVE/issues/663
58 							Value = ((string)source).Replace("\\r\\n", Environment.NewLine).Replace("\\x20", " ");
59 						}
60 					}
61 				}
62 
63 				internal class Group
64 				{
65 					internal readonly string Id;
66 					internal readonly Group[] Groups;
67 					internal readonly Unit[] Units;
68 
Group(XNamespace xmlns, XElement group, string languageCode)69 					internal Group(XNamespace xmlns, XElement group, string languageCode)
70 					{
71 						Id = (string)group.Attribute("id");
72 						Groups = group.Elements(xmlns + "group").Select(g => new Group(xmlns, g, languageCode)).ToArray();
73 						Units = group.Elements(xmlns + "trans-unit").Select(t => new Unit(xmlns, t, languageCode)).Where(t => !string.IsNullOrEmpty(t.Value)).ToArray();
74 					}
75 				}
76 
77 				internal readonly Group[] Groups;
78 				internal readonly Unit[] Units;
79 
XliffFile(TextReader reader, string languageCode)80 				internal XliffFile(TextReader reader, string languageCode)
81 				{
82 					XDocument xml = XDocument.Load(reader);
83 					XNamespace xmlns = xml.Root.Name.Namespace;
84 					XElement body = xml.Root.Element(xmlns + "file").Element(xmlns + "body");
85 
86 					Groups = body.Elements(xmlns + "group").Select(g => new Group(xmlns, g, languageCode)).ToArray();
87 					Units = body.Elements(xmlns + "trans-unit").Select(t => new Unit(xmlns, t, languageCode)).Where(t => !string.IsNullOrEmpty(t.Value)).ToArray();
88 				}
89 			}
90 
91 			/// <summary>Creates a new language from a file stream</summary>
92 			/// <param name="languageReader">The file stream</param>
93 			/// <param name="languageCode">The language code</param>
Language(TextReader languageReader, string languageCode)94 			internal Language(TextReader languageReader, string languageCode)
95 			{
96 				Name = "Unknown";
97 				LanguageCode = languageCode;
98 				FallbackCodes = new List<string> { "en-US" };
99 				myCommandInfos = new CommandInfo[CommandInfos.Length];
100 				KeyInfos = new KeyInfo[TranslatedKeys.Length];
101 				myQuickReferences = new InterfaceQuickReference();
102 				Array.Copy(CommandInfos, myCommandInfos, myCommandInfos.Length);
103 				Array.Copy(TranslatedKeys, KeyInfos, TranslatedKeys.Length);
104 
105 				string prefix = string.Empty;
106 				XliffFile file = new XliffFile(languageReader, languageCode);
107 				List<InterfaceString> strings = new List<InterfaceString>();
108 
109 				ExportUnits(prefix, file.Units, strings);
110 
111 				foreach (XliffFile.Group group in file.Groups)
112 				{
113 					ExportGroup(prefix, group, strings);
114 				}
115 
116 				InterfaceString[] groupLanguage = strings.Where(s => s.Name.StartsWith("language_")).ToArray();
117 
118 				foreach (var interfaceString in groupLanguage)
119 				{
120 					string key = interfaceString.Name.Split('_')[1];
121 
122 					switch (key)
123 					{
124 						case "name":
125 							Name = interfaceString.Text;
126 							strings.Remove(interfaceString);
127 							break;
128 						case "flag":
129 							Flag = interfaceString.Text;
130 							strings.Remove(interfaceString);
131 							break;
132 					}
133 				}
134 
135 				InterfaceString[] groupOpenBve = strings.Where(s => s.Name.StartsWith("openbve_")).ToArray();
136 
137 				foreach (var interfaceString in groupOpenBve)
138 				{
139 					string section = interfaceString.Name.Split('_')[1];
140 					string key = string.Join("_", interfaceString.Name.Split('_').Skip(2));
141 
142 					switch (section)
143 					{
144 						case "handles":
145 							switch (key)
146 							{
147 								case "forward":
148 									myQuickReferences.HandleForward = interfaceString.Text;
149 									strings.Remove(interfaceString);
150 									break;
151 								case "neutral":
152 									myQuickReferences.HandleNeutral = interfaceString.Text;
153 									strings.Remove(interfaceString);
154 									break;
155 								case "backward":
156 									myQuickReferences.HandleBackward = interfaceString.Text;
157 									strings.Remove(interfaceString);
158 									break;
159 								case "power":
160 									myQuickReferences.HandlePower = interfaceString.Text;
161 									strings.Remove(interfaceString);
162 									break;
163 								case "powernull":
164 									myQuickReferences.HandlePowerNull = interfaceString.Text;
165 									strings.Remove(interfaceString);
166 									break;
167 								case "brake":
168 									myQuickReferences.HandleBrake = interfaceString.Text;
169 									strings.Remove(interfaceString);
170 									break;
171 								case "locobrake":
172 									myQuickReferences.HandleLocoBrake = interfaceString.Text;
173 									strings.Remove(interfaceString);
174 									break;
175 								case "brakenull":
176 									myQuickReferences.HandleBrakeNull = interfaceString.Text;
177 									strings.Remove(interfaceString);
178 									break;
179 								case "release":
180 									myQuickReferences.HandleRelease = interfaceString.Text;
181 									strings.Remove(interfaceString);
182 									break;
183 								case "lap":
184 									myQuickReferences.HandleLap = interfaceString.Text;
185 									strings.Remove(interfaceString);
186 									break;
187 								case "service":
188 									myQuickReferences.HandleService = interfaceString.Text;
189 									strings.Remove(interfaceString);
190 									break;
191 								case "emergency":
192 									myQuickReferences.HandleEmergency = interfaceString.Text;
193 									strings.Remove(interfaceString);
194 									break;
195 								case "holdbrake":
196 									myQuickReferences.HandleHoldBrake = interfaceString.Text;
197 									strings.Remove(interfaceString);
198 									break;
199 							}
200 							break;
201 						case "doors":
202 							switch (key)
203 							{
204 								case "left":
205 									myQuickReferences.DoorsLeft = interfaceString.Text;
206 									strings.Remove(interfaceString);
207 									break;
208 								case "right":
209 									myQuickReferences.DoorsRight = interfaceString.Text;
210 									strings.Remove(interfaceString);
211 									break;
212 							}
213 							break;
214 						case "misc":
215 							switch (key)
216 							{
217 								case "score":
218 									myQuickReferences.Score = interfaceString.Text;
219 									strings.Remove(interfaceString);
220 									break;
221 							}
222 							break;
223 						case "commands":
224 							for (int k = 0; k < myCommandInfos.Length; k++)
225 							{
226 								if (string.Compare(myCommandInfos[k].Name, key, StringComparison.OrdinalIgnoreCase) == 0)
227 								{
228 									myCommandInfos[k].Description = interfaceString.Text;
229 									strings.Remove(interfaceString);
230 									break;
231 								}
232 							}
233 							break;
234 						case "keys":
235 							for (int k = 0; k < KeyInfos.Length; k++)
236 							{
237 								if (string.Compare(KeyInfos[k].Name, key, StringComparison.OrdinalIgnoreCase) == 0)
238 								{
239 									KeyInfos[k].Description = interfaceString.Text;
240 									strings.Remove(interfaceString);
241 									break;
242 								}
243 							}
244 							break;
245 						case "fallback":
246 							switch (key)
247 							{
248 								case "language":
249 									FallbackCodes.Add(interfaceString.Text);
250 									strings.Remove(interfaceString);
251 									break;
252 							}
253 							break;
254 					}
255 				}
256 
257 				InterfaceStrings = strings.ToArray();
258 
259 				for (int i = 0; i < InterfaceStrings.Length; i++)
260 				{
261 					if (InterfaceStrings[i].Name.StartsWith("openbve_"))
262 					{
263 						InterfaceStrings[i].Name = InterfaceStrings[i].Name.Replace("openbve_", string.Empty);
264 					}
265 				}
266 			}
267 
ExportUnits(string prefix, XliffFile.Unit[] units, List<InterfaceString> strings)268 			private void ExportUnits(string prefix, XliffFile.Unit[] units, List<InterfaceString> strings)
269 			{
270 				strings.AddRange(units.Select(u => new InterfaceString
271 				{
272 					Name = prefix + u.Id,
273 					Text = u.Value
274 				}));
275 			}
276 
ExportGroup(string prefix, XliffFile.Group group, List<InterfaceString> strings)277 			private void ExportGroup(string prefix, XliffFile.Group group, List<InterfaceString> strings)
278 			{
279 				prefix += group.Id + "_";
280 
281 				ExportUnits(prefix, group.Units, strings);
282 
283 				foreach (XliffFile.Group childGroup in group.Groups)
284 				{
285 					ExportGroup(prefix, childGroup, strings);
286 				}
287 			}
288 
289 
290 			/// <summary>Always returns the textual name of the language</summary>
ToString()291 			public override string ToString()
292 			{
293 				return Name;
294 			}
295 		}
296 	}
297 }
298