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