• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/ap/gpl/timemachine/gettext-0.17/gettext-runtime/intl-csharp/
1/* GNU gettext for C#
2 * Copyright (C) 2003, 2005, 2007 Free Software Foundation, Inc.
3 * Written by Bruno Haible <bruno@clisp.org>, 2003.
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Library General Public License as published
7 * by the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 * USA.
19 */
20
21/*
22 * Using the GNU gettext approach, compiled message catalogs are assemblies
23 * containing just one class, a subclass of GettextResourceSet. They are thus
24 * interoperable with standard ResourceManager based code.
25 *
26 * The main differences between the common .NET resources approach and the
27 * GNU gettext approach are:
28 * - In the .NET resource approach, the keys are abstract textual shortcuts.
29 *   In the GNU gettext approach, the keys are the English/ASCII version
30 *   of the messages.
31 * - In the .NET resource approach, the translation files are called
32 *   "Resource.locale.resx" and are UTF-8 encoded XML files. In the GNU gettext
33 *   approach, the translation files are called "Resource.locale.po" and are
34 *   in the encoding the translator has chosen. There are at least three GUI
35 *   translating tools (Emacs PO mode, KDE KBabel, GNOME gtranslator).
36 * - In the .NET resource approach, the function ResourceManager.GetString
37 *   returns an empty string or throws an InvalidOperationException when no
38 *   translation is found. In the GNU gettext approach, the GetString function
39 *   returns the (English) message key in that case.
40 * - In the .NET resource approach, there is no support for plural handling.
41 *   In the GNU gettext approach, we have the GetPluralString function.
42 * - In the .NET resource approach, there is no support for context specific
43 *   translations.
44 *   In the GNU gettext approach, we have the GetParticularString function.
45 *
46 * To compile GNU gettext message catalogs into C# assemblies, the msgfmt
47 * program can be used.
48 */
49
50using System; /* String, InvalidOperationException, Console */
51using System.Globalization; /* CultureInfo */
52using System.Resources; /* ResourceManager, ResourceSet, IResourceReader */
53using System.Reflection; /* Assembly, ConstructorInfo */
54using System.Collections; /* Hashtable, ICollection, IEnumerator, IDictionaryEnumerator */
55using System.IO; /* Path, FileNotFoundException, Stream */
56using System.Text; /* StringBuilder */
57
58namespace GNU.Gettext {
59
60  /// <summary>
61  /// Each instance of this class can be used to lookup translations for a
62  /// given resource name. For each <c>CultureInfo</c>, it performs the lookup
63  /// in several assemblies, from most specific over territory-neutral to
64  /// language-neutral.
65  /// </summary>
66  public class GettextResourceManager : ResourceManager {
67
68    // ======================== Public Constructors ========================
69
70    /// <summary>
71    /// Constructor.
72    /// </summary>
73    /// <param name="baseName">the resource name, also the assembly base
74    ///                        name</param>
75    public GettextResourceManager (String baseName)
76      : base (baseName, Assembly.GetCallingAssembly(), typeof (GettextResourceSet)) {
77    }
78
79    /// <summary>
80    /// Constructor.
81    /// </summary>
82    /// <param name="baseName">the resource name, also the assembly base
83    ///                        name</param>
84    public GettextResourceManager (String baseName, Assembly assembly)
85      : base (baseName, assembly, typeof (GettextResourceSet)) {
86    }
87
88    // ======================== Implementation ========================
89
90    /// <summary>
91    /// Loads and returns a satellite assembly.
92    /// </summary>
93    // This is like Assembly.GetSatelliteAssembly, but uses resourceName
94    // instead of assembly.GetName().Name, and works around a bug in
95    // mono-0.28.
96    private static Assembly GetSatelliteAssembly (Assembly assembly, String resourceName, CultureInfo culture) {
97      String satelliteExpectedLocation =
98        Path.GetDirectoryName(assembly.Location)
99        + Path.DirectorySeparatorChar + culture.Name
100        + Path.DirectorySeparatorChar + resourceName + ".resources.dll";
101      return Assembly.LoadFrom(satelliteExpectedLocation);
102    }
103
104    /// <summary>
105    /// Loads and returns the satellite assembly for a given culture.
106    /// </summary>
107    private Assembly MySatelliteAssembly (CultureInfo culture) {
108      return GetSatelliteAssembly(MainAssembly, BaseName, culture);
109    }
110
111    /// <summary>
112    /// Converts a resource name to a class name.
113    /// </summary>
114    /// <returns>a nonempty string consisting of alphanumerics and underscores
115    ///          and starting with a letter or underscore</returns>
116    private static String ConstructClassName (String resourceName) {
117      // We could just return an arbitrary fixed class name, like "Messages",
118      // assuming that every assembly will only ever contain one
119      // GettextResourceSet subclass, but this assumption would break the day
120      // we want to support multi-domain PO files in the same format...
121      bool valid = (resourceName.Length > 0);
122      for (int i = 0; valid && i < resourceName.Length; i++) {
123        char c = resourceName[i];
124        if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
125              || (i > 0 && c >= '0' && c <= '9')))
126          valid = false;
127      }
128      if (valid)
129        return resourceName;
130      else {
131        // Use hexadecimal escapes, using the underscore as escape character.
132        String hexdigit = "0123456789abcdef";
133        StringBuilder b = new StringBuilder();
134        b.Append("__UESCAPED__");
135        for (int i = 0; i < resourceName.Length; i++) {
136          char c = resourceName[i];
137          if (c >= 0xd800 && c < 0xdc00
138              && i+1 < resourceName.Length
139              && resourceName[i+1] >= 0xdc00 && resourceName[i+1] < 0xe000) {
140            // Combine two UTF-16 words to a character.
141            char c2 = resourceName[i+1];
142            int uc = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
143            b.Append('_');
144            b.Append('U');
145            b.Append(hexdigit[(uc >> 28) & 0x0f]);
146            b.Append(hexdigit[(uc >> 24) & 0x0f]);
147            b.Append(hexdigit[(uc >> 20) & 0x0f]);
148            b.Append(hexdigit[(uc >> 16) & 0x0f]);
149            b.Append(hexdigit[(uc >> 12) & 0x0f]);
150            b.Append(hexdigit[(uc >> 8) & 0x0f]);
151            b.Append(hexdigit[(uc >> 4) & 0x0f]);
152            b.Append(hexdigit[uc & 0x0f]);
153            i++;
154          } else if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
155                       || (c >= '0' && c <= '9'))) {
156            int uc = c;
157            b.Append('_');
158            b.Append('u');
159            b.Append(hexdigit[(uc >> 12) & 0x0f]);
160            b.Append(hexdigit[(uc >> 8) & 0x0f]);
161            b.Append(hexdigit[(uc >> 4) & 0x0f]);
162            b.Append(hexdigit[uc & 0x0f]);
163          } else
164            b.Append(c);
165        }
166        return b.ToString();
167      }
168    }
169
170    /// <summary>
171    /// Instantiates a resource set for a given culture.
172    /// </summary>
173    /// <exception cref="ArgumentException">
174    ///   The expected type name is not valid.
175    /// </exception>
176    /// <exception cref="ReflectionTypeLoadException">
177    ///   satelliteAssembly does not contain the expected type.
178    /// </exception>
179    /// <exception cref="NullReferenceException">
180    ///   The type has no no-arguments constructor.
181    /// </exception>
182    private static GettextResourceSet InstantiateResourceSet (Assembly satelliteAssembly, String resourceName, CultureInfo culture) {
183      // We expect a class with a culture dependent class name.
184      Type clazz = satelliteAssembly.GetType(ConstructClassName(resourceName)+"_"+culture.Name.Replace('-','_'));
185      // We expect it has a no-argument constructor, and invoke it.
186      ConstructorInfo constructor = clazz.GetConstructor(Type.EmptyTypes);
187      return (GettextResourceSet) constructor.Invoke(null);
188    }
189
190    private static GettextResourceSet[] EmptyResourceSetArray = new GettextResourceSet[0];
191
192    // Cache for already loaded GettextResourceSet cascades.
193    private Hashtable /* CultureInfo -> GettextResourceSet[] */ Loaded = new Hashtable();
194
195    /// <summary>
196    /// Returns the array of <c>GettextResourceSet</c>s for a given culture,
197    /// loading them if necessary, and maintaining the cache.
198    /// </summary>
199    private GettextResourceSet[] GetResourceSetsFor (CultureInfo culture) {
200      //Console.WriteLine(">> GetResourceSetsFor "+culture);
201      // Look up in the cache.
202      GettextResourceSet[] result = (GettextResourceSet[]) Loaded[culture];
203      if (result == null) {
204        lock(this) {
205          // Look up again - maybe another thread has filled in the entry
206          // while we slept waiting for the lock.
207          result = (GettextResourceSet[]) Loaded[culture];
208          if (result == null) {
209            // Determine the GettextResourceSets for the given culture.
210            if (culture.Parent == null || culture.Equals(CultureInfo.InvariantCulture))
211              // Invariant culture.
212              result = EmptyResourceSetArray;
213            else {
214              // Use a satellite assembly as primary GettextResourceSet, and
215              // the result for the parent culture as fallback.
216              GettextResourceSet[] parentResult = GetResourceSetsFor(culture.Parent);
217              Assembly satelliteAssembly;
218              try {
219                satelliteAssembly = MySatelliteAssembly(culture);
220              } catch (FileNotFoundException e) {
221                satelliteAssembly = null;
222              }
223              if (satelliteAssembly != null) {
224                GettextResourceSet satelliteResourceSet;
225                try {
226                  satelliteResourceSet = InstantiateResourceSet(satelliteAssembly, BaseName, culture);
227                } catch (Exception e) {
228                  Console.Error.WriteLine(e);
229                  Console.Error.WriteLine(e.StackTrace);
230                  satelliteResourceSet = null;
231                }
232                if (satelliteResourceSet != null) {
233                  result = new GettextResourceSet[1+parentResult.Length];
234                  result[0] = satelliteResourceSet;
235                  Array.Copy(parentResult, 0, result, 1, parentResult.Length);
236                } else
237                  result = parentResult;
238              } else
239                result = parentResult;
240            }
241            // Put the result into the cache.
242            Loaded.Add(culture, result);
243          }
244        }
245      }
246      //Console.WriteLine("<< GetResourceSetsFor "+culture);
247      return result;
248    }
249
250    /*
251    /// <summary>
252    /// Releases all loaded <c>GettextResourceSet</c>s and their assemblies.
253    /// </summary>
254    // TODO: No way to release an Assembly?
255    public override void ReleaseAllResources () {
256      ...
257    }
258    */
259
260    /// <summary>
261    /// Returns the translation of <paramref name="msgid"/> in a given culture.
262    /// </summary>
263    /// <param name="msgid">the key string to be translated, an ASCII
264    ///                     string</param>
265    /// <returns>the translation of <paramref name="msgid"/>, or
266    ///          <paramref name="msgid"/> if none is found</returns>
267    public override String GetString (String msgid, CultureInfo culture) {
268      foreach (GettextResourceSet rs in GetResourceSetsFor(culture)) {
269        String translation = rs.GetString(msgid);
270        if (translation != null)
271          return translation;
272      }
273      // Fallback.
274      return msgid;
275    }
276
277    /// <summary>
278    /// Returns the translation of <paramref name="msgid"/> and
279    /// <paramref name="msgidPlural"/> in a given culture, choosing the right
280    /// plural form depending on the number <paramref name="n"/>.
281    /// </summary>
282    /// <param name="msgid">the key string to be translated, an ASCII
283    ///                     string</param>
284    /// <param name="msgidPlural">the English plural of <paramref name="msgid"/>,
285    ///                           an ASCII string</param>
286    /// <param name="n">the number, should be &gt;= 0</param>
287    /// <returns>the translation, or <paramref name="msgid"/> or
288    ///          <paramref name="msgidPlural"/> if none is found</returns>
289    public virtual String GetPluralString (String msgid, String msgidPlural, long n, CultureInfo culture) {
290      foreach (GettextResourceSet rs in GetResourceSetsFor(culture)) {
291        String translation = rs.GetPluralString(msgid, msgidPlural, n);
292        if (translation != null)
293          return translation;
294      }
295      // Fallback: Germanic plural form.
296      return (n == 1 ? msgid : msgidPlural);
297    }
298
299    // ======================== Public Methods ========================
300
301    /// <summary>
302    /// Returns the translation of <paramref name="msgid"/> in the context
303    /// of <paramref name="msgctxt"/> a given culture.
304    /// </summary>
305    /// <param name="msgctxt">the context for the key string, an ASCII
306    ///                       string</param>
307    /// <param name="msgid">the key string to be translated, an ASCII
308    ///                     string</param>
309    /// <returns>the translation of <paramref name="msgid"/>, or
310    ///          <paramref name="msgid"/> if none is found</returns>
311    public String GetParticularString (String msgctxt, String msgid, CultureInfo culture) {
312      String combined = msgctxt + "\u0004" + msgid;
313      foreach (GettextResourceSet rs in GetResourceSetsFor(culture)) {
314        String translation = rs.GetString(combined);
315        if (translation != null)
316          return translation;
317      }
318      // Fallback.
319      return msgid;
320    }
321
322    /// <summary>
323    /// Returns the translation of <paramref name="msgid"/> and
324    /// <paramref name="msgidPlural"/> in the context of
325    /// <paramref name="msgctxt"/> in a given culture, choosing the right
326    /// plural form depending on the number <paramref name="n"/>.
327    /// </summary>
328    /// <param name="msgctxt">the context for the key string, an ASCII
329    ///                       string</param>
330    /// <param name="msgid">the key string to be translated, an ASCII
331    ///                     string</param>
332    /// <param name="msgidPlural">the English plural of <paramref name="msgid"/>,
333    ///                           an ASCII string</param>
334    /// <param name="n">the number, should be &gt;= 0</param>
335    /// <returns>the translation, or <paramref name="msgid"/> or
336    ///          <paramref name="msgidPlural"/> if none is found</returns>
337    public virtual String GetParticularPluralString (String msgctxt, String msgid, String msgidPlural, long n, CultureInfo culture) {
338      String combined = msgctxt + "\u0004" + msgid;
339      foreach (GettextResourceSet rs in GetResourceSetsFor(culture)) {
340        String translation = rs.GetPluralString(combined, msgidPlural, n);
341        if (translation != null)
342          return translation;
343      }
344      // Fallback: Germanic plural form.
345      return (n == 1 ? msgid : msgidPlural);
346    }
347
348    /// <summary>
349    /// Returns the translation of <paramref name="msgid"/> in the current
350    /// culture.
351    /// </summary>
352    /// <param name="msgid">the key string to be translated, an ASCII
353    ///                     string</param>
354    /// <returns>the translation of <paramref name="msgid"/>, or
355    ///          <paramref name="msgid"/> if none is found</returns>
356    public override String GetString (String msgid) {
357      return GetString(msgid, CultureInfo.CurrentUICulture);
358    }
359
360    /// <summary>
361    /// Returns the translation of <paramref name="msgid"/> and
362    /// <paramref name="msgidPlural"/> in the current culture, choosing the
363    /// right plural form depending on the number <paramref name="n"/>.
364    /// </summary>
365    /// <param name="msgid">the key string to be translated, an ASCII
366    ///                     string</param>
367    /// <param name="msgidPlural">the English plural of <paramref name="msgid"/>,
368    ///                           an ASCII string</param>
369    /// <param name="n">the number, should be &gt;= 0</param>
370    /// <returns>the translation, or <paramref name="msgid"/> or
371    ///          <paramref name="msgidPlural"/> if none is found</returns>
372    public virtual String GetPluralString (String msgid, String msgidPlural, long n) {
373      return GetPluralString(msgid, msgidPlural, n, CultureInfo.CurrentUICulture);
374    }
375
376    /// <summary>
377    /// Returns the translation of <paramref name="msgid"/> in the context
378    /// of <paramref name="msgctxt"/> in the current culture.
379    /// </summary>
380    /// <param name="msgctxt">the context for the key string, an ASCII
381    ///                       string</param>
382    /// <param name="msgid">the key string to be translated, an ASCII
383    ///                     string</param>
384    /// <returns>the translation of <paramref name="msgid"/>, or
385    ///          <paramref name="msgid"/> if none is found</returns>
386    public String GetParticularString (String msgctxt, String msgid) {
387      return GetParticularString(msgctxt, msgid, CultureInfo.CurrentUICulture);
388    }
389
390    /// <summary>
391    /// Returns the translation of <paramref name="msgid"/> and
392    /// <paramref name="msgidPlural"/> in the context of
393    /// <paramref name="msgctxt"/> in the current culture, choosing the
394    /// right plural form depending on the number <paramref name="n"/>.
395    /// </summary>
396    /// <param name="msgctxt">the context for the key string, an ASCII
397    ///                       string</param>
398    /// <param name="msgid">the key string to be translated, an ASCII
399    ///                     string</param>
400    /// <param name="msgidPlural">the English plural of <paramref name="msgid"/>,
401    ///                           an ASCII string</param>
402    /// <param name="n">the number, should be &gt;= 0</param>
403    /// <returns>the translation, or <paramref name="msgid"/> or
404    ///          <paramref name="msgidPlural"/> if none is found</returns>
405    public virtual String GetParticularPluralString (String msgctxt, String msgid, String msgidPlural, long n) {
406      return GetParticularPluralString(msgctxt, msgid, msgidPlural, n, CultureInfo.CurrentUICulture);
407    }
408
409  }
410
411  /// <summary>
412  /// <para>
413  /// Each instance of this class encapsulates a single PO file.
414  /// </para>
415  /// <para>
416  /// This API of this class is not meant to be used directly; use
417  /// <c>GettextResourceManager</c> instead.
418  /// </para>
419  /// </summary>
420  // We need this subclass of ResourceSet, because the plural formula must come
421  // from the same ResourceSet as the object containing the plural forms.
422  public class GettextResourceSet : ResourceSet {
423
424    /// <summary>
425    /// Creates a new message catalog. When using this constructor, you
426    /// must override the <c>ReadResources</c> method, in order to initialize
427    /// the <c>Table</c> property. The message catalog will support plural
428    /// forms only if the <c>ReadResources</c> method installs values of type
429    /// <c>String[]</c> and if the <c>PluralEval</c> method is overridden.
430    /// </summary>
431    protected GettextResourceSet ()
432      : base (DummyResourceReader) {
433    }
434
435    /// <summary>
436    /// Creates a new message catalog, by reading the string/value pairs from
437    /// the given <paramref name="reader"/>. The message catalog will support
438    /// plural forms only if the reader can produce values of type
439    /// <c>String[]</c> and if the <c>PluralEval</c> method is overridden.
440    /// </summary>
441    public GettextResourceSet (IResourceReader reader)
442      : base (reader) {
443    }
444
445    /// <summary>
446    /// Creates a new message catalog, by reading the string/value pairs from
447    /// the given <paramref name="stream"/>, which should have the format of
448    /// a <c>.resources</c> file. The message catalog will not support plural
449    /// forms.
450    /// </summary>
451    public GettextResourceSet (Stream stream)
452      : base (stream) {
453    }
454
455    /// <summary>
456    /// Creates a new message catalog, by reading the string/value pairs from
457    /// the file with the given <paramref name="fileName"/>. The file should
458    /// be in the format of a <c>.resources</c> file. The message catalog will
459    /// not support plural forms.
460    /// </summary>
461    public GettextResourceSet (String fileName)
462      : base (fileName) {
463    }
464
465    /// <summary>
466    /// Returns the translation of <paramref name="msgid"/>.
467    /// </summary>
468    /// <param name="msgid">the key string to be translated, an ASCII
469    ///                     string</param>
470    /// <returns>the translation of <paramref name="msgid"/>, or <c>null</c> if
471    ///          none is found</returns>
472    // The default implementation essentially does (String)Table[msgid].
473    // Here we also catch the plural form case.
474    public override String GetString (String msgid) {
475      Object value = GetObject(msgid);
476      if (value == null || value is String)
477        return (String)value;
478      else if (value is String[])
479        // A plural form, but no number is given.
480        // Like the C implementation, return the first plural form.
481        return ((String[]) value)[0];
482      else
483        throw new InvalidOperationException("resource for \""+msgid+"\" in "+GetType().FullName+" is not a string");
484    }
485
486    /// <summary>
487    /// Returns the translation of <paramref name="msgid"/>, with possibly
488    /// case-insensitive lookup.
489    /// </summary>
490    /// <param name="msgid">the key string to be translated, an ASCII
491    ///                     string</param>
492    /// <returns>the translation of <paramref name="msgid"/>, or <c>null</c> if
493    ///          none is found</returns>
494    // The default implementation essentially does (String)Table[msgid].
495    // Here we also catch the plural form case.
496    public override String GetString (String msgid, bool ignoreCase) {
497      Object value = GetObject(msgid, ignoreCase);
498      if (value == null || value is String)
499        return (String)value;
500      else if (value is String[])
501        // A plural form, but no number is given.
502        // Like the C implementation, return the first plural form.
503        return ((String[]) value)[0];
504      else
505        throw new InvalidOperationException("resource for \""+msgid+"\" in "+GetType().FullName+" is not a string");
506    }
507
508    /// <summary>
509    /// Returns the translation of <paramref name="msgid"/> and
510    /// <paramref name="msgidPlural"/>, choosing the right plural form
511    /// depending on the number <paramref name="n"/>.
512    /// </summary>
513    /// <param name="msgid">the key string to be translated, an ASCII
514    ///                     string</param>
515    /// <param name="msgidPlural">the English plural of <paramref name="msgid"/>,
516    ///                           an ASCII string</param>
517    /// <param name="n">the number, should be &gt;= 0</param>
518    /// <returns>the translation, or <c>null</c> if none is found</returns>
519    public virtual String GetPluralString (String msgid, String msgidPlural, long n) {
520      Object value = GetObject(msgid);
521      if (value == null || value is String)
522        return (String)value;
523      else if (value is String[]) {
524        String[] choices = (String[]) value;
525        long index = PluralEval(n);
526        return choices[index >= 0 && index < choices.Length ? index : 0];
527      } else
528        throw new InvalidOperationException("resource for \""+msgid+"\" in "+GetType().FullName+" is not a string");
529    }
530
531    /// <summary>
532    /// Returns the index of the plural form to be chosen for a given number.
533    /// The default implementation is the Germanic plural formula:
534    /// zero for <paramref name="n"/> == 1, one for <paramref name="n"/> != 1.
535    /// </summary>
536    protected virtual long PluralEval (long n) {
537      return (n == 1 ? 0 : 1);
538    }
539
540    /// <summary>
541    /// Returns the keys of this resource set, i.e. the strings for which
542    /// <c>GetObject()</c> can return a non-null value.
543    /// </summary>
544    public virtual ICollection Keys {
545      get {
546        return Table.Keys;
547      }
548    }
549
550    /// <summary>
551    /// A trivial instance of <c>IResourceReader</c> that does nothing.
552    /// </summary>
553    // Needed by the no-arguments constructor.
554    private static IResourceReader DummyResourceReader = new DummyIResourceReader();
555
556  }
557
558  /// <summary>
559  /// A trivial <c>IResourceReader</c> implementation.
560  /// </summary>
561  class DummyIResourceReader : IResourceReader {
562
563    // Implementation of IDisposable.
564    void System.IDisposable.Dispose () {
565    }
566
567    // Implementation of IEnumerable.
568    IEnumerator System.Collections.IEnumerable.GetEnumerator () {
569      return null;
570    }
571
572    // Implementation of IResourceReader.
573    void System.Resources.IResourceReader.Close () {
574    }
575    IDictionaryEnumerator System.Resources.IResourceReader.GetEnumerator () {
576      return null;
577    }
578
579  }
580
581}
582