001: /*
002: * Copyright 2006-2007 Pentaho Corporation. All rights reserved.
003: * This software was developed by Pentaho Corporation and is provided under the terms
004: * of the Mozilla Public License, Version 1.1, or any later version. You may not use
005: * this file except in compliance with the license. If you need a copy of the license,
006: * please go to http://www.mozilla.org/MPL/MPL-1.1.txt.
007: *
008: * Software distributed under the Mozilla Public License is distributed on an "AS IS"
009: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
010: * the license for the specific language governing your rights and limitations.
011: *
012: * Additional Contributor(s): Martin Schmid gridvision engineering GmbH
013: */
014: package org.pentaho.reportdesigner.lib.client.i18n;
015:
016: import org.jetbrains.annotations.NonNls;
017: import org.jetbrains.annotations.NotNull;
018: import org.jetbrains.annotations.Nullable;
019: import org.jetbrains.annotations.PropertyKey;
020:
021: import java.text.Collator;
022: import java.text.MessageFormat;
023: import java.util.Collection;
024: import java.util.Collections;
025: import java.util.HashMap;
026: import java.util.HashSet;
027: import java.util.Locale;
028: import java.util.ResourceBundle;
029: import java.util.TreeSet;
030: import java.util.logging.Level;
031: import java.util.logging.Logger;
032:
033: /**
034: * User: Martin
035: * Date: 05.03.2005
036: * Time: 06:29:18
037: */
038: public class TranslationManager {
039: @NotNull
040: @NonNls
041: public static final String COMMON_BUNDLE_PREFIX = "COMMON_BUNDLE_PREFIX";
042:
043: @NotNull
044: @NonNls
045: private static final Logger LOG = Logger
046: .getLogger(TranslationManager.class.getName());
047:
048: @NotNull
049: private static final TranslationManager instance = new TranslationManager();
050:
051: @NotNull
052: public static TranslationManager getInstance() {
053: return instance;
054: }
055:
056: @NotNull
057: private TreeSet<String> missingKeys;
058: @NotNull
059: private HashMap<String, ResourceBundle> prefixBundleMap;
060: @NotNull
061: private HashSet<Locale> supportedLocales;
062:
063: private TranslationManager() {
064: Collator collator = Collator.getInstance();
065: collator.setStrength(Collator.PRIMARY);
066: missingKeys = new TreeSet<String>(collator);
067:
068: prefixBundleMap = new HashMap<String, ResourceBundle>();
069: supportedLocales = new HashSet<Locale>();
070: }
071:
072: public void addBundle(@NotNull
073: @NonNls
074: String prefix, @NotNull
075: ResourceBundle resourceBundle) throws BundleAlreadyExistsException {
076: if (prefixBundleMap.containsKey(prefix)) {
077: throw new BundleAlreadyExistsException();
078: }
079:
080: prefixBundleMap.put(prefix, resourceBundle);
081: }
082:
083: @NotNull
084: public Locale[] getSupportedLocales() {
085: Locale[] localeList = new Locale[supportedLocales.size()];
086: localeList = supportedLocales.toArray(localeList);
087:
088: return localeList;
089: }
090:
091: public void addSupportedLocale(@NotNull
092: Locale locale) {
093: supportedLocales.add(locale);
094: }
095:
096: @NotNull
097: private String getRawTranslation(@NotNull
098: @NonNls
099: String prefix, @Nullable
100: @NonNls
101: String key, @NotNull
102: @NonNls
103: Object... parameters) {
104: if (key == null) {
105: if (LOG.isLoggable(Level.FINE)) //noinspection ThrowableInstanceNeverThrown
106: LOG.log(Level.FINE,
107: "TranslationManager.getTranslation " + prefix,
108: new RuntimeException("key = null, locale = "
109: + Locale.getDefault()));
110: return "no key defined";//NON-NLS
111: }
112:
113: String fixdeKey = key;
114:
115: if (key.contains(" ")) {
116: fixdeKey = key.replace(' ', '_');
117: }
118:
119: try {
120: ResourceBundle bundle = prefixBundleMap.get(prefix);
121: if (bundle != null) {
122: String text = bundle.getString(key);
123:
124: if (text != null) {
125: if (parameters.length > 0) {
126: for (int i = 0; i < parameters.length; i++) {
127: Object parameter = parameters[i];
128: if (parameter instanceof String) {
129: String p = (String) parameter;
130: if (p.indexOf('_') != -1) {
131: parameters[i] = p
132: .replace("_", "__");
133: }
134: }
135: }
136:
137: text = MessageFormat.format(text, parameters);
138: }
139: return text;
140: }
141: }
142: } catch (Throwable e) {
143: if (LOG.isLoggable(Level.FINE))
144: LOG.log(Level.FINE,
145: "TranslationManager.getTranslation ", e);
146: //TODO do the right thing, perhaps it is contained in the common bundle, but catching throwable seems to be a bit broad
147: //TODO we can remove the whole fallback strategy as it is only used in case of a user using the wrong prefix (?)
148: //TODO would be better to tell the user that he is using the wrong prefix
149: }
150: //
151: //if (!COMMON_BUNDLE_PREFIX.equals(prefix))
152: //{
153: // try
154: // {
155: // ResourceBundle bundle = prefixBundleMap.get(COMMON_BUNDLE_PREFIX);
156: // if (bundle != null)
157: // {
158: // String text = bundle.getString(key);
159: //
160: // if (text != null)
161: // {
162: // if (parameters.length > 0)
163: // {
164: // text = MessageFormat.format(text, parameters);
165: // }
166: // return text;
167: // }
168: // }
169: // }
170: // catch (Throwable e)
171: // {
172: // if (LOG.isLoggable(Level.FINE)) LOG.log(Level.FINE, "TranslationManager.getTranslation e = " + e);
173: // }
174: //}
175:
176: if (parameters.length > 0) {
177: StringBuilder stringBuilder = new StringBuilder(fixdeKey);
178: stringBuilder.append(" (");
179: for (Object o : parameters) {
180: stringBuilder.append(o.getClass().getName());
181: stringBuilder.append(", ");
182: }
183: stringBuilder.delete(stringBuilder.length() - 2,
184: stringBuilder.length());
185: stringBuilder.append(")");
186: missingKeys.add(stringBuilder.toString());
187: } else {
188: missingKeys.add(fixdeKey);
189: }
190:
191: return key;
192: }
193:
194: @NotNull
195: public String getTranslation(@NotNull
196: @NonNls
197: String prefix, @PropertyKey(resourceBundle="res.Translations")
198: @NotNull
199: @NonNls
200: String key, @NotNull
201: @NonNls
202: Object... parameters) {
203: String text = getRawTranslation(prefix, key, parameters);
204: //remove all underscores not escaped by underscore
205: StringBuilder sb = new StringBuilder(text.length());
206: for (int i = 0; i < text.length() - 1; i++) {
207: char c1 = text.charAt(i);
208: char c2 = text.charAt(i + 1);
209:
210: if (c1 == '_' && c2 != '_') {
211: //ignore
212: } else if (c1 == '_'/* && c2 == '_'*/) {
213: //was escaped underscore, append one underscore and skip the second
214: sb.append('_');
215: i++;
216: } else {
217: sb.append(c1);
218: }
219: }
220: if (text.length() > 0 && text.charAt(text.length() - 1) != '_') {
221: sb.append(text.charAt(text.length() - 1));//append last char whatever it might be
222: }
223:
224: text = sb.toString();
225: return text;
226: }
227:
228: public int getMnemonic(@NotNull
229: @NonNls
230: String prefix, @NotNull
231: @NonNls
232: String key, @NotNull
233: @NonNls
234: Object... parameters) {
235: String translation = getRawTranslation(prefix, key, parameters);
236: if (translation.equals(key)) {
237: return 0;
238: } else if (translation.length() == 0) {
239: return 0;
240: } else if (translation.length() == 1) {
241: return translation.charAt(0);
242: } else {
243: //try to find a mnemonic (character prefixed with an underscore, and not escaped with an additional underscore)
244: for (int i = 0; i < translation.length() - 1; i++) {
245: char c1 = translation.charAt(i);
246: char c2 = translation.charAt(i + 1);
247:
248: if (c1 == '_' && c2 != '_') {
249: return Character.toUpperCase(c2);
250: } else if (c1 == '_'/* && c2 == '_'*/) {
251: i++;
252: }
253: }
254: return 0;
255: }
256: }
257:
258: public int getDisplayedMnemonicIndex(@NotNull
259: @NonNls
260: String prefix, @NotNull
261: @NonNls
262: String key, @NotNull
263: @NonNls
264: Object... parameters) {
265: String translation = getRawTranslation(prefix, key, parameters);
266: //try to find a mnemonic (character prefixed with an underscore, and not escaped with an additional underscore)
267: for (int i = 0; i < translation.length() - 1; i++) {
268: char c1 = translation.charAt(i);
269: char c2 = translation.charAt(i + 1);
270:
271: if (c1 == '_' && c2 != '_') {
272: return i;
273: } else if (c1 == '_'/* && c2 == '_'*/) {
274: i++;
275: }
276: }
277: return -1;
278: }
279:
280: @NotNull
281: public Collection<String> getMissingKeysList() {
282: return Collections.unmodifiableCollection(missingKeys);
283: }
284:
285: }
|