1 /* See license.txt for terms of usage */
  2 
  3 // ********************************************************************************************* //
  4 // Constants
  5 
  6 const Cc = Components.classes;
  7 const Ci = Components.interfaces;
  8 const Cu = Components.utils;
  9 
 10 const DEFAULT_LOCALE = "en-US";
 11 
 12 var EXPORTED_SYMBOLS = [];
 13 
 14 // ********************************************************************************************* //
 15 // Services
 16 
 17 Cu.import("resource://firebug/fbtrace.js");
 18 Cu.import("resource://gre/modules/Services.jsm");
 19 Cu.import("resource://firebug/prefLoader.js");
 20 Cu.import("resource://gre/modules/PluralForm.jsm");
 21 
 22 // ********************************************************************************************* //
 23 // Firebug UI Localization
 24 
 25 var stringBundleService = Services.strings;
 26 var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
 27 
 28 // This module
 29 var Locale = {};
 30 
 31 /*
 32  * $STR - intended for localization of a static string.
 33  * $STRF - intended for localization of a string with dynamically inserted values.
 34  * $STRP - intended for localization of a string with dynamically plural forms.
 35  *
 36  * Notes:
 37  * 1) Name with _ in place of spaces is the key in the firebug.properties file.
 38  * 2) If the specified key isn't localized for particular language, both methods use
 39  *    the part after the last dot (in the specified name) as the return value.
 40  *
 41  * Examples:
 42  * $STR("Label"); - search for key "Label" within the firebug.properties file
 43  *                 and returns its value. If the key doesn't exist returns "Label".
 44  *
 45  * $STR("Button Label"); - search for key "Button_Label" withing the firebug.properties
 46  *                        file. If the key doesn't exist returns "Button Label".
 47  *
 48  * $STR("net.Response Header"); - search for key "net.Response_Header". If the key doesn't
 49  *                               exist returns "Response Header".
 50  *
 51  * firebug.properties:
 52  * net.timing.Request_Time=Request Time: %S [%S]
 53  *
 54  * var param1 = 10;
 55  * var param2 = "ms";
 56  * $STRF("net.timing.Request Time", param1, param2);  -> "Request Time: 10 [ms]"
 57  *
 58  * - search for key "net.timing.Request_Time" within the firebug.properties file. Parameters
 59  *   are inserted at specified places (%S) in the same order as they are passed. If the
 60  *   key doesn't exist the method returns "Request Time".
 61  */
 62 Locale.$STR = function(name, bundle)
 63 {
 64     var strKey = name.replace(" ", "_", "g");
 65 
 66     if (!PrefLoader.getPref("useDefaultLocale"))
 67     {
 68         try
 69         {
 70             if (bundle)
 71                 return bundle.getString(strKey);
 72             else
 73                 return Locale.getStringBundle().GetStringFromName(strKey);
 74         }
 75         catch (err)
 76         {
 77             if (FBTrace.DBG_LOCALE)
 78                 FBTrace.sysout("lib.getString FAILS '" + name + "'", err);
 79         }
 80     }
 81 
 82     try
 83     {
 84         // The en-US string should be always available.
 85         var defaultBundle = Locale.getDefaultStringBundle();
 86         if (defaultBundle)
 87             return defaultBundle.GetStringFromName(strKey);
 88     }
 89     catch (err)
 90     {
 91         if (FBTrace.DBG_LOCALE)
 92             FBTrace.sysout("lib.getString (default) FAILS '" + name + "'", err);
 93     }
 94 
 95     // Don't panic now and use only the label after last dot.
 96     var index = name.lastIndexOf(".");
 97     if (index > 0 && name.charAt(index-1) != "\\")
 98         name = name.substr(index + 1);
 99     name = name.replace("_", " ", "g");
100     return name;
101 }
102 
103 Locale.$STRF = function(name, args, bundle)
104 {
105     var strKey = name.replace(" ", "_", "g");
106 
107     if (!PrefLoader.getPref("useDefaultLocale"))
108     {
109         try
110         {
111             if (bundle)
112                 return bundle.getFormattedString(strKey, args);
113             else
114                 return Locale.getStringBundle().formatStringFromName(strKey, args, args.length);
115         }
116         catch (err)
117         {
118             if (FBTrace.DBG_LOCALE)
119                 FBTrace.sysout("lib.getString FAILS '" + name + "'", err);
120         }
121     }
122 
123     try
124     {
125         // The en-US string should be always available.
126         var defaultBundle = Locale.getDefaultStringBundle();
127         if (defaultBundle)
128             return defaultBundle.formatStringFromName(strKey, args, args.length);
129     }
130     catch (err)
131     {
132         if (FBTrace.DBG_LOCALE)
133             FBTrace.sysout("lib.getString (default) FAILS '" + name + "'", err);
134     }
135 
136     // Don't panic now and use only the label after last dot.
137     var index = name.lastIndexOf(".");
138     if (index > 0)
139         name = name.substr(index + 1);
140 
141     return name;
142 }
143 
144 Locale.$STRP = function(name, args, index, bundle)
145 {
146     // xxxHonza:
147     // pluralRule from chrome://global/locale/intl.properties for Chinese is 1,
148     // which is wrong, it should be 0.
149 
150     var getPluralForm = PluralForm.get;
151     var getNumForms = PluralForm.numForms;
152 
153     // Get custom plural rule; otherwise the rule from chrome://global/locale/intl.properties
154     // (depends on the current locale) is used.
155     var pluralRule = Locale.getPluralRule();
156     if (!isNaN(parseInt(pluralRule, 10)))
157         [getPluralForm, getNumForms] = PluralForm.makeGetter(pluralRule);
158 
159     // Index of the argument with plural form (there must be only one arg that needs plural form).
160     if (!index)
161         index = 0;
162 
163     // Get proper plural form from the string (depends on the current Firefox locale).
164     var translatedString = Locale.$STRF(name, args, bundle);
165     if (translatedString.search(";") > 0)
166         return getPluralForm(args[index], translatedString);
167 
168     // translatedString contains no ";", either rule 0 or getString fails
169     return translatedString;
170 }
171 
172 /*
173  * Use the current value of the attribute as a key to look up the localized value.
174  */
175 Locale.internationalize = function(element, attr, args)
176 {
177     if (element)
178     {
179         var xulString = element.getAttribute(attr);
180         if (xulString)
181         {
182             var localized = args ? Locale.$STRF(xulString, args) : Locale.$STR(xulString);
183             // Set localized value of the attribute only if it exists.
184             if (localized)
185                 element.setAttribute(attr, localized);
186         }
187     }
188     else
189     {
190         if (FBTrace.DBG_LOCALE)
191             FBTrace.sysout("Failed to internationalize element with attr "+attr+" args:"+args);
192     }
193 }
194 
195 Locale.internationalizeElements = function(doc, elements, attributes)
196 {
197     for (var i=0; i<elements.length; i++)
198     {
199         var element = elements[i];
200 
201         if (typeof(elements) == "string")
202             element = doc.getElementById(elements[i]);
203 
204         if (!element)
205             continue;
206 
207         // Remove fbInternational class, so that the label is not translated again later.
208         element.classList.remove("fbInternational");
209 
210         for (var j=0; j<attributes.length; j++)
211         {
212             if (element.hasAttribute(attributes[j]))
213                 Locale.internationalize(element, attributes[j]);
214         }
215     }
216 }
217 
218 Locale.registerStringBundle = function(bundleURI)
219 {
220     // Notice that this category entry must not be persistent in Fx 4.0
221     categoryManager.addCategoryEntry("strings_firebug", bundleURI, "", false, true);
222     this.stringBundle = null;
223 
224     bundleURI = getDefaultStringBundleURI(bundleURI);
225     categoryManager.addCategoryEntry("default_strings_firebug", bundleURI, "", false, true);
226     this.defaultStringBundle = null;
227 }
228 
229 Locale.getStringBundle = function()
230 {
231     if (!this.stringBundle)
232         this.stringBundle = stringBundleService.createExtensibleBundle("strings_firebug");
233     return this.stringBundle;
234 }
235 
236 Locale.getDefaultStringBundle = function()
237 {
238     if (!this.defaultStringBundle)
239         this.defaultStringBundle = stringBundleService.createExtensibleBundle("default_strings_firebug");
240     return this.defaultStringBundle;
241 }
242 
243 Locale.getPluralRule = function()
244 {
245     try
246     {
247         return this.getStringBundle().GetStringFromName("pluralRule");
248     }
249     catch (err)
250     {
251     }
252 }
253 
254 // ********************************************************************************************* //
255 // Helpers
256 
257 function getDefaultStringBundleURI(bundleURI)
258 {
259     var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
260         getService(Ci.nsIChromeRegistry);
261 
262     var uri = Services.io.newURI(bundleURI, "UTF-8", null);
263     var fileURI = chromeRegistry.convertChromeURL(uri).spec;
264     var parts = fileURI.split("/");
265     parts[parts.length - 2] = DEFAULT_LOCALE;
266 
267     return parts.join("/");
268 }
269 
270 // ********************************************************************************************* //
271