source: trunk/eraser/Eraser.Util/Localisation.cs @ 1964

Revision 1802, 9.1 KB checked in by lowjoel, 5 years ago (diff)

Merged the CodeReview? Branch back to trunk. (Finally!) Closes #275: Code Review.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25
26using System.IO;
27using System.Reflection;
28using System.Globalization;
29using System.Windows.Forms;
30using System.Diagnostics;
31using System.Resources;
32using System.Threading;
33
34namespace Eraser.Util
35{
36    public static class Localisation
37    {
38        /// <summary>
39        /// Returns true if the given control is right-to-left reading.
40        /// </summary>
41        /// <param name="control">The control to query.</param>
42        /// <returns>True if the control is right-to-left reading.</returns>
43        public static bool IsRightToLeft(Control control)
44        {
45            while (control != null)
46            {
47                switch (control.RightToLeft)
48                {
49                    case RightToLeft.No:
50                        return false;
51                    case RightToLeft.Yes:
52                        return true;
53                    default:
54                        control = control.Parent;
55                        break;
56                }
57            }
58
59            if (Application.OpenForms.Count > 0)
60            {
61                return IsRightToLeft(Application.OpenForms[0]);
62            }
63            else
64            {
65                using (Form form = new Form())
66                    return IsRightToLeft(form);
67            }
68        }
69
70        /// <summary>
71        /// Translates the localisable string to the set localised string.
72        /// </summary>
73        /// <param name="text">The string to localise.</param>
74        /// <param name="assembly">The assembly from which localised resource satellite
75        /// assemblies should be loaded from.</param>
76        /// <returns>A localised string, or str if no localisation exists.</returns>
77        public static string TranslateText(string text, Assembly assembly)
78        {
79            //If the string is empty, forget it!
80            if (text.Length == 0)
81                return text;
82
83            //First get the dictionary mapping assemblies and ResourceManagers (i.e. pick out
84            //the dictionary with ResourceManagers representing the current culture.)
85            if (!managers.ContainsKey(Thread.CurrentThread.CurrentUICulture))
86                managers[Thread.CurrentThread.CurrentUICulture] =
87                    new Dictionary<Assembly, ResourceManager>();
88            Dictionary<Assembly, ResourceManager> assemblies = managers[
89                Thread.CurrentThread.CurrentUICulture];
90
91            //Then look for the ResourceManager dealing with the calling assembly's
92            //resources
93            ResourceManager res = null;
94            if (!assemblies.ContainsKey(assembly))
95            {
96                //Load the resource DLL. The resource DLL is located in the <LanguageName-RegionName>
97                //subfolder of the folder containing the main assembly
98                string languageID = string.Empty;
99                Assembly languageAssembly = LoadLanguage(Thread.CurrentThread.CurrentUICulture,
100                    assembly, out languageID);
101
102                //If we found the language assembly to load, then we load it directly, otherwise
103                //fall back to the invariant culture.
104                string resourceName = Path.GetFileNameWithoutExtension(assembly.Location) +
105                    ".Strings" + (languageID.Length != 0 ? ("." + languageID) : "");
106                res = new ResourceManager(resourceName,
107                    languageAssembly != null ? languageAssembly : assembly);
108                assemblies[assembly] = res;
109            }
110            else
111                res = assemblies[assembly];
112
113            string result = res.GetString(Escape(text), Thread.CurrentThread.CurrentUICulture);
114#if DEBUG
115            return string.IsNullOrEmpty(result) ? text : Unescape(result);
116#else
117            return string.IsNullOrEmpty(result) || result == "(Untranslated)" ? text : Unescape(result);
118#endif
119        }
120
121        /// <summary>
122        /// Gets whether the provided translation exists for the provided assembly.
123        /// </summary>
124        /// <param name="culture">The exact language to check for.</param>
125        /// <param name="assembly">The assembly to check for the presence of a localisation.</param>
126        /// <returns>True if the resource assembly for the given culture and assembly exists.</returns>
127        public static bool LocalisationExists(CultureInfo culture, Assembly assembly)
128        {
129            return File.Exists(Path.Combine(
130                Path.Combine(Path.GetDirectoryName(assembly.Location), culture.Name), //Directory
131                Path.GetFileNameWithoutExtension(assembly.Location) + ".resources.dll"));
132        }
133
134        /// <summary>
135        /// Retrieves all present language plugins
136        /// </summary>
137        /// <returns>A list, with an instance of each Language class</returns>
138        public static IList<CultureInfo> Localisations
139        {
140            get
141            {
142                List<CultureInfo> result = new List<CultureInfo>();
143                Assembly assembly = Assembly.GetEntryAssembly();
144                foreach (CultureInfo info in CultureInfo.GetCultures(CultureTypes.AllCultures))
145                {
146                    if (string.IsNullOrEmpty(info.Name))
147                        continue;
148                    else if (LocalisationExists(info, assembly))
149                        result.Add(info);
150                }
151
152                //Last resort
153                if (result.Count == 0)
154                    result.Add(CultureInfo.GetCultureInfo("EN"));
155                return result.AsReadOnly();
156            }
157        }
158
159        /// <summary>
160        /// Replaces non-printable codes used in the string to translate into translatable placeholders.
161        /// </summary>
162        /// <param name="str">The string to escape</param>
163        /// <returns>An escaped string</returns>
164        private static string Escape(string str)
165        {
166            return str.Replace("\n", "\\n").Replace("\r", "\\r");
167        }
168
169        /// <summary>
170        /// Replaces all escape codes used in the translated string into real character codes.
171        /// </summary>
172        /// <param name="str">The string to unescape</param>
173        /// <returns>An unescaped string</returns>
174        private static string Unescape(string str)
175        {
176            return str.Replace("\\n", "\n").Replace("\\r", "\r");
177        }
178
179        /// <summary>
180        /// Looks in the folder denoted by <paramref name="path"/> for the resource providing
181        /// resources for <paramref name="culture"/>. The name of the resource DLL will be the
182        /// culture name &gt;languagecode2-country/regioncode2&lt;.
183        /// </summary>
184        /// <param name="culture">The culture to load.</param>
185        /// <param name="assembly">The assembly to look for localised resources for.</param>
186        /// <returns>An assembly containing the required resources, or null.</returns>
187        private static Assembly LoadLanguage(CultureInfo culture, Assembly assembly,
188            out string languageID)
189        {
190            languageID = string.Empty;
191            string path = string.Empty;
192            while (culture != CultureInfo.InvariantCulture)
193            {
194                path = Path.Combine(Path.GetDirectoryName(assembly.Location), culture.Name);
195                if (Directory.Exists(path))
196                {
197                    string assemblyPath = Path.Combine(path,
198                        Path.GetFileNameWithoutExtension(assembly.Location) + ".resources.dll");
199                    if (File.Exists(assemblyPath))
200                    {
201                        languageID = culture.Name;
202                        return Assembly.LoadFrom(assemblyPath);
203                    }
204                }
205                culture = culture.Parent;
206            }
207
208            return null;
209        }
210
211        private static Dictionary<CultureInfo, Dictionary<Assembly, ResourceManager>> managers =
212            new Dictionary<CultureInfo, Dictionary<Assembly, ResourceManager>>();
213    }
214
215    /// <summary>
216    /// Internationalisation class. Instead of calling GetString on all strings, just
217    /// call S._(string) or S._(string, object) for plurals
218    /// </summary>
219    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "S")]
220    public static class S
221    {
222        /// <summary>
223        /// Translates the localisable string to the set localised string.
224        /// </summary>
225        /// <param name="str">The string to localise.</param>
226        /// <returns>A localised string, or str if no localisation exists.</returns>
227        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
228        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "_")]
229        public static string _(string text)
230        {
231            return Localisation.TranslateText(text, Assembly.GetCallingAssembly());
232        }
233
234        /// <summary>
235        /// Translates the localisable text to the localised text, formatting all
236        /// placeholders using composite formatting. This is shorthand for
237        /// <code>string.Format(S._(text), args)</code>
238        /// </summary>
239        /// <param name="text">The text to localise.</param>
240        /// <param name="args">Arguments for the composite formatting call.</param>
241        /// <returns>The formatted and localised string.</returns>
242        /// <remarks>The localised string is retrieved before formatting.</remarks>
243        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
244        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "_")]
245        public static string _(string text, params object[] args)
246        {
247            //Get the localised version of the input string.
248            string localStr = Localisation.TranslateText(text, Assembly.GetCallingAssembly());
249
250            //Format the string.
251            return string.Format(CultureInfo.CurrentCulture, localStr, args);
252        }
253    }
254}
Note: See TracBrowser for help on using the repository browser.