Mercurial > repos > IBBoard
comparison Lang/Translation.cs @ 6:f269d8bcc152
Re #2 - Refactor API
* Refactor translations in to smaller and clearer functions
* Add "get current translation language" function
* Add documentation of public methods
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 27 Dec 2008 20:28:04 +0000 |
parents | f9ec2be467fe |
children | f4da31cb09d9 |
comparison
equal
deleted
inserted
replaced
5:5ee4956dae76 | 6:f269d8bcc152 |
---|---|
1 using System; | 1 using System; |
2 using System.IO; | 2 using System.IO; |
3 using System.Xml; | 3 using System.Xml; |
4 using System.Xml.Schema; | 4 using System.Xml.Schema; |
5 using System.Collections.Generic; | 5 using System.Collections.Generic; |
6 using System.Windows.Forms; | |
7 using System.Reflection; | 6 using System.Reflection; |
8 using System.ComponentModel; | 7 using System.ComponentModel; |
9 using IBBoard.IO; | 8 using IBBoard.IO; |
10 using IBBoard.Logging; | 9 using IBBoard.Logging; |
11 using IBBoard.Xml; | 10 using IBBoard.Xml; |
12 | 11 |
13 namespace IBBoard.Lang | 12 namespace IBBoard.Lang |
14 { | 13 { |
15 /// <summary> | 14 /// <summary> |
16 /// Summary description for Translation. | 15 /// A basic string translator that loads a default language and a specified language and returns translated strings that correspond to translation IDs. |
16 /// If the string doesn't exist in the specified language then the translator falls back to the default language. If the translation doesn't exist in the default language | |
17 /// then either a supplied value or a "no validation available" message is returned. | |
17 /// </summary> | 18 /// </summary> |
18 public class Translation | 19 public class Translation |
19 { | 20 { |
21 private static readonly string DEFAULT_LANGUAGE = "en"; | |
22 private static readonly string DIVIDER_STRING = "-"; | |
20 private static string lang = ""; | 23 private static string lang = ""; |
21 private static DirectoryInfo translationDir = null; | 24 private static DirectoryInfo translationDir; |
22 private static Dictionary<string, string> translationsLocal = null; | 25 private static Dictionary<string, string> translationsLocal; |
23 private static Dictionary<string, string> translationsDefault = null; | 26 private static Dictionary<string, string> translationsDefault; |
24 | 27 |
28 /// <summary> | |
29 /// Initialises the translations for the language specified and the default translations so that the Translation class can be used | |
30 /// </summary> | |
31 /// <param name="appPath"> | |
32 /// The full path that the application is running from. Must contain the "translations" folder. | |
33 /// </param> | |
34 /// <param name="language"> | |
35 /// The language to use as the load language | |
36 /// </param> | |
25 public static void InitialiseTranslations(string appPath, string language) | 37 public static void InitialiseTranslations(string appPath, string language) |
26 { | 38 { |
27 string translationPath = appPath+Constants.DirectoryString+"translations"; | 39 InitialiseDefaults(appPath); |
40 FileInfo file = GetTranslationFile(DEFAULT_LANGUAGE); | |
41 XmlDocument doc = LoadTranslationDocument(file); | |
42 LoadTranslationsFromDocument(doc, translationsDefault); | |
43 translationsLocal = null; | |
44 LoadTranslation(lang); | |
45 } | |
46 | |
47 private static void InitialiseDefaults(string appPath) | |
48 { | |
49 string translationPath = appPath.TrimEnd(Constants.DirectoryChar) + Constants.DirectoryString + "translations"; | |
28 | 50 |
29 if (Directory.Exists(translationPath)) | 51 if (Directory.Exists(translationPath)) |
30 { | 52 { |
53 translationsDefault = new Dictionary<string,string>(); | |
54 translationsLocal = new Dictionary<string,string>(); | |
31 translationDir = new DirectoryInfo(translationPath); | 55 translationDir = new DirectoryInfo(translationPath); |
32 | 56 } |
33 FileInfo file = new FileInfo(translationDir.FullName.TrimEnd(Constants.DirectoryChar)+Constants.DirectoryString+"en.translation"); | 57 else |
34 | 58 { |
35 if (!File.Exists(file.FullName)) | 59 throw new ArgumentException("Translation path must exist ("+translationPath+")"); |
60 } | |
61 } | |
62 | |
63 private static XmlDocument LoadTranslationDocument(FileInfo file) | |
64 { | |
65 XmlDocument doc = new XmlDocument(); | |
66 XmlReaderSettings settings = new XmlReaderSettings(); | |
67 settings.XmlResolver = new IBBXmlResolver(translationDir.Parent.FullName); | |
68 settings.ValidationType = ValidationType.DTD; | |
69 settings.ProhibitDtd = false; | |
70 settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod); | |
71 XmlReader valReader = XmlReader.Create(file.FullName, settings); | |
72 doc.Load(valReader); | |
73 valReader.Close(); | |
74 return doc; | |
75 } | |
76 | |
77 private static FileInfo GetTranslationFile(string language) | |
78 { | |
79 FileInfo file = new FileInfo(translationDir.FullName + Constants.DirectoryString + language + ".translation"); | |
80 | |
81 if (!file.Exists) | |
82 { | |
83 throw new FileNotFoundException(language + "translation could not be found in "+translationDir.FullName); | |
84 } | |
85 | |
86 return file; | |
87 } | |
88 | |
89 private static void LoadTranslationsFromDocument(XmlDocument doc, Dictionary<string, string> translationTable) | |
90 { | |
91 try | |
92 { | |
93 XmlNodeList translations = doc.GetElementsByTagName("translation"); | |
94 | |
95 if (translations.Count==0) | |
36 { | 96 { |
37 throw new FileNotFoundException("en.translation could not be found in "+translationDir.FullName); | 97 throw new InvalidFileException("No translations found in "+GetLanguageOfDocument(doc)+".translation"); |
38 } | 98 } |
39 | 99 |
40 translationsDefault = new Dictionary<string,string>(); | 100 Dictionary<string, string> tempTranslationTable = new Dictionary<string,string>(); |
41 | 101 |
42 XmlDocument doc = new XmlDocument(); | 102 foreach (XmlNode node in translations) |
43 XmlReaderSettings settings = new XmlReaderSettings(); | |
44 settings.XmlResolver = new IBBXmlResolver(appPath); | |
45 settings.ValidationType = ValidationType.DTD; | |
46 settings.ProhibitDtd = false; | |
47 settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod); | |
48 XmlReader valReader = XmlReader.Create(file.FullName, settings); | |
49 doc.Load(valReader); | |
50 valReader.Close(); | |
51 | |
52 try | |
53 { | 103 { |
54 XmlNodeList translations = doc.GetElementsByTagName("translation"); | 104 tempTranslationTable.Add(node.Attributes["id"].Value, node.InnerText); |
55 | |
56 if (translations.Count==0) | |
57 { | |
58 throw new InvalidFileException("No translations found in "+language+".translation"); | |
59 } | |
60 | |
61 foreach (XmlNode node in translations) | |
62 { | |
63 translationsDefault.Add(node.Attributes["id"].Value, node.InnerText); | |
64 } | |
65 } | 105 } |
66 catch(Exception ex) | 106 |
107 translationTable.Clear(); | |
108 | |
109 foreach (string key in tempTranslationTable.Keys) | |
67 { | 110 { |
68 throw new XmlParseException("Error while parsing "+file.FullName+": "+ex.Message); | 111 string translation; |
69 } | 112 tempTranslationTable.TryGetValue(key, out translation); |
70 | 113 translationTable.Add(key, translation); |
71 translationsLocal = null; | 114 } |
72 LoadTranslation(lang); | 115 } |
73 } | 116 catch(Exception ex) |
74 else | 117 { |
75 { | 118 throw new XmlParseException("Error while parsing " + GetLanguageOfDocument(doc)+" translation: "+ex.Message); |
76 throw new ArgumentException("Translation path must exist ("+translationPath+")"); | 119 } |
77 } | 120 } |
121 | |
122 private static string GetLanguageOfDocument(XmlDocument doc) | |
123 { | |
124 return doc != null ? doc.DocumentElement.GetAttribute("lang") : ""; | |
78 } | 125 } |
79 | 126 |
80 private static void ValidationEventMethod(object sender, ValidationEventArgs e) | 127 private static void ValidationEventMethod(object sender, ValidationEventArgs e) |
81 { | 128 { |
82 //TODO: Fire a validation failure event | 129 //TODO: Fire a validation failure event |
83 LogNotifier.Error(typeof(Translation), "Validation Error", e.Exception); | 130 LogNotifier.Error(typeof(Translation), "Validation Error", e.Exception); |
84 } | 131 } |
85 | 132 |
133 /// <summary> | |
134 /// Loads translations for a given language and sets them as the local language | |
135 /// </summary> | |
136 /// <param name="translationLang"> | |
137 /// The new local language to load | |
138 /// </param> | |
86 public static void LoadTranslation(string translationLang) | 139 public static void LoadTranslation(string translationLang) |
87 { | 140 { |
88 checkInitialisation(); | 141 checkInitialisation(); |
89 | 142 |
90 if (translationLang!="" && translationLang!="en") | 143 if (translationLang == "" || translationLang == null) |
91 { | 144 { |
92 FileInfo file = new FileInfo(translationDir.FullName.TrimEnd(Constants.DirectoryChar)+Constants.DirectoryString+translationLang+".translation"); | 145 throw new ArgumentException("Translation language cannot be empty or null"); |
93 | 146 } |
94 if (!File.Exists(file.FullName)) | 147 |
95 { | 148 if (translationLang != DEFAULT_LANGUAGE) |
96 throw new FileNotFoundException(translationLang+".translation could not be found in "+translationDir.FullName); | 149 { |
97 } | 150 FileInfo file = GetTranslationFile(translationLang); |
98 | 151 XmlDocument doc = LoadTranslationDocument(file); |
99 XmlDocument doc = new XmlDocument(); | 152 LoadTranslationsFromDocument(doc, translationsLocal); |
100 XmlReaderSettings settings = new XmlReaderSettings(); | |
101 settings.XmlResolver = new IBBXmlResolver(translationDir.Parent.FullName); | |
102 settings.ValidationType = ValidationType.DTD; | |
103 settings.ProhibitDtd = false; | |
104 settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod); | |
105 XmlReader valReader = XmlReader.Create(file.FullName, settings); | |
106 doc.Load(valReader); | |
107 valReader.Close(); | |
108 Dictionary<string, string> tempTranslations = new Dictionary<string, string>(); | |
109 | |
110 try | |
111 { | |
112 XmlNodeList translations = doc.GetElementsByTagName("translation"); | |
113 | |
114 if (translations.Count==0) | |
115 { | |
116 throw new InvalidFileException("No translations found in "+translationLang+".translation"); | |
117 } | |
118 | |
119 foreach (XmlNode node in translations) | |
120 { | |
121 tempTranslations.Add(node.Attributes["id"].Value, node.InnerText); | |
122 } | |
123 } | |
124 catch(XmlParseException) | |
125 { | |
126 throw; | |
127 } | |
128 catch(Exception ex) | |
129 { | |
130 throw new XmlParseException("Error while parsing "+file.FullName+": "+ex.Message); | |
131 } | |
132 | |
133 translationsLocal = tempTranslations; | |
134 lang = translationLang; | |
135 } | 153 } |
136 else | 154 else |
137 { | 155 { |
138 lang = translationLang; | 156 translationsLocal.Clear(); |
139 } | 157 } |
140 } | 158 |
141 | 159 lang = translationLang; |
160 } | |
161 | |
162 /// <summary> | |
163 /// Gets a translation for a given ID, falling back to a "missing translation" message if none can be found. Also optionally replaces any placeholders with the supplied values. | |
164 /// </summary> | |
165 /// <param name="translationID"> | |
166 /// The ID to look up the translation for | |
167 /// </param> | |
168 /// <param name="replacements"> | |
169 /// A collection of <see cref="System.Object"/>s to replace placeholders with | |
170 /// </param> | |
171 /// <returns> | |
172 /// The translation with the placeholders replaced or a "missing translation" message | |
173 /// </returns> | |
142 public static string GetTranslation(string translationID, params object[] replacements) | 174 public static string GetTranslation(string translationID, params object[] replacements) |
143 { | 175 { |
144 return GetTranslation(translationID, false, replacements); | 176 return GetTranslation(translationID, false, replacements); |
145 } | 177 } |
146 | 178 |
179 /// <summary> | |
180 /// Gets a translation for a given ID, falling back to null or a warning message if a translation cannot be found. Also optionally replaces any placeholders with the supplied values. | |
181 /// </summary> | |
182 /// <param name="translationID"> | |
183 /// The ID to look up the translation for | |
184 /// </param> | |
185 /// <param name="returnNullOnFail"> | |
186 /// TRUE if null should be returned when no translation can be found, or FALSE if a "missing translation" message should be returned | |
187 /// </param> | |
188 /// <param name="replacements"> | |
189 /// A collection of <see cref="System.Object"/>s to replace placeholders with | |
190 /// </param> | |
191 /// <returns> | |
192 /// The translation with the placeholders replaced, or a "missing translation" message or null depending on <param name="returnNullOnFail"> | |
193 /// </returns> | |
147 public static string GetTranslation(string translationID, bool returnNullOnFail, params object[] replacements) | 194 public static string GetTranslation(string translationID, bool returnNullOnFail, params object[] replacements) |
148 { | 195 { |
149 return GetTranslation(translationID, returnNullOnFail ? null : "", replacements); | 196 return GetTranslation(translationID, returnNullOnFail ? null : "", replacements); |
150 } | 197 } |
151 | 198 |
199 /// <summary> | |
200 /// Gets a translation for a given ID, falling back to a supplied default if a translation cannot be found. Also optionally replaces any placeholders with the supplied values. | |
201 /// </summary> | |
202 /// <param name="translationID"> | |
203 /// The ID to look up the translation for | |
204 /// </param> | |
205 /// <param name="defaultTranslation"> | |
206 /// The string to return if no translation can be found. Can be null or any string. | |
207 /// </param> | |
208 /// <param name="replacements"> | |
209 /// A collection of <see cref="System.Object"/>s to replace placeholders with | |
210 /// </param> | |
211 /// <returns> | |
212 /// The translation, if one exists, or the supplied default with the placeholders replaced | |
213 /// </returns> | |
152 public static string GetTranslation(string translationID, string defaultTranslation, params object[] replacements) | 214 public static string GetTranslation(string translationID, string defaultTranslation, params object[] replacements) |
153 { | 215 { |
154 checkInitialisation(); | 216 checkInitialisation(); |
155 string trans = null; | 217 string trans = GetTranslationFromTables(translationID); |
156 | 218 |
219 if (trans == null) | |
220 { | |
221 trans = GetDefaultTranslation(translationID, defaultTranslation); | |
222 } | |
223 | |
224 trans = AddVariablesToTranslation(trans, replacements); | |
225 | |
226 return trans; | |
227 } | |
228 | |
229 private static string GetTranslationFromTables(string translationID) | |
230 { | |
231 string translation = null; | |
232 | |
157 if (translationsLocal!=null) | 233 if (translationsLocal!=null) |
158 { | 234 { |
159 translationsLocal.TryGetValue(translationID, out trans); | 235 translationsLocal.TryGetValue(translationID, out translation); |
160 } | 236 } |
161 | 237 |
162 if (trans == null) | 238 if (translation == null) |
163 { | 239 { |
164 translationsDefault.TryGetValue(translationID, out trans); | 240 translationsDefault.TryGetValue(translationID, out translation); |
165 } | 241 } |
166 | 242 |
167 if (trans == null) | 243 return translation; |
168 { | 244 } |
169 //no translation so fall back to the provided default if not an empty string | 245 |
170 if (defaultTranslation!="") | 246 private static string GetDefaultTranslation(string translationID, string defaultTranslation) |
171 { | 247 { |
172 trans = defaultTranslation; | 248 return (defaultTranslation != "" && defaultTranslation != null) ? defaultTranslation : GetMissingTranslationMessage(translationID); |
173 } | 249 } |
174 else | 250 |
175 { | 251 private static string GetMissingTranslationMessage(string translationID) |
176 trans = "++ Missing Translation "+translationID+" ++"; | 252 { |
177 } | 253 return "++ Missing Translation "+translationID+" ++"; |
178 } | 254 } |
179 | 255 |
180 if (trans != null) | 256 private static string AddVariablesToTranslation(string translation, object[] replacements) |
181 { | 257 { |
182 if (replacements!=null && replacements.Length > 0) | 258 if (translation != null && replacements != null && replacements.Length > 0) |
183 { | 259 { |
184 trans = String.Format(trans, replacements); | 260 translation = String.Format(translation, replacements); |
185 } | 261 } |
186 //else no need to do anything | 262 |
187 } | 263 return translation; |
188 | 264 } |
189 return trans; | 265 |
190 } | |
191 | |
192 private static void checkInitialisation() | 266 private static void checkInitialisation() |
193 { | 267 { |
194 if (translationDir==null) | 268 if (translationDir==null) |
195 { | 269 { |
196 throw new InvalidOperationException("Translation class has not been initialised"); | 270 throw new InvalidOperationException("Translation class has not been initialised"); |
197 } | 271 } |
198 } | 272 } |
199 | 273 |
274 /// <summary> | |
275 /// Translate an <see cref="ITranslatable"/> item, with optional string replacement | |
276 /// </summary> | |
277 /// <param name="item"> | |
278 /// A <see cref="ITranslatable"/> to set the text for | |
279 /// </param> | |
280 /// <param name="replacements"> | |
281 /// A collection of <see cref="System.Object"/>s that will be used to fill place-holders | |
282 /// </param> | |
200 public static void Translate(ITranslatable item, params object[] replacements) | 283 public static void Translate(ITranslatable item, params object[] replacements) |
201 { | 284 { |
202 if (item.Text=="" || item.Text=="-") | 285 if (item.Text == "" || item.Text == DIVIDER_STRING) |
203 { | 286 { |
204 //if it doesn't need translating - either no text for the Dev or it's a hyphen for a divider - then bail early | 287 //it doesn't need translating - either there is no text from the developer or it's a hyphen for a divider |
205 return; | 288 return; |
206 } | 289 } |
207 | 290 |
208 item.Text = GetTranslation(item.Name, replacements); | 291 item.Text = GetTranslation(item.Name, replacements); |
292 } | |
293 | |
294 /// <summary> | |
295 /// Get the current local translation language. This is an arbitrary string used in the translation file's name and will not necessarily match the ISO language code. | |
296 /// </summary> | |
297 /// <returns> | |
298 /// The string used in the file name of the current local translation | |
299 /// </returns> | |
300 public static string GetTranslationLanguage() | |
301 { | |
302 return lang; | |
209 } | 303 } |
210 } | 304 } |
211 } | 305 } |