Statistics
| Revision:

root / branches / v10 / extensions / extI18n / src / org / gvsig / i18n / impl / I18nManagerImpl.java @ 26065

History | View | Annotate | Download (19.9 KB)

1
/* gvSIG. Geographic Information System of the Valencian Government
2
 *
3
 * Copyright (C) 2007-2008 Infrastructures and Transports Department
4
 * of the Valencian Gobernment (CIT)
5
 * 
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 2
9
 * of the License, or (at your option) any later version.
10
 * 
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 * 
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
19
 * MA  02110-1301, USA.
20
 * 
21
 */
22

    
23
/*
24
 * AUTHORS (In addition to CIT):
25
 * 2008 {DiSiD Technologies}  {New extension for installation and update of text translations}
26
 */
27
package org.gvsig.i18n.impl;
28

    
29
import java.io.*;
30
import java.util.*;
31
import java.util.Map.Entry;
32
import java.util.jar.*;
33
import java.util.zip.ZipEntry;
34

    
35
import org.gvsig.i18n.*;
36

    
37
import com.iver.andami.Launcher;
38
import com.iver.andami.PluginServices;
39
import com.iver.andami.config.generate.AndamiConfig;
40
import com.iver.utiles.StringUtilities;
41
import com.iver.utiles.XMLEntity;
42

    
43
/**
44
 * Implementation of the I18nManager interface.
45
 * 
46
 * @author <a href="mailto:dcervera@disid.com">David Cervera</a>
47
 * @author <a href="mailto:cordin@disid.com">C?sar Ordi?ana</a>
48
 */
49
public class I18nManagerImpl implements I18nManager {
50

    
51
    private static final String MF_LOCALE_VARIANT = "locale-variant";
52

    
53
    private static final String MF_LOCALE_COUNTRY = "locale-country";
54

    
55
    private static final String MF_LOCALE_LANGUAGE = "locale-language";
56

    
57
    private static final String MF_REFLOCALE_VARIANT = "reference-locale-variant";
58

    
59
    private static final String MF_REFLOCALE_COUNTRY = "reference-locale-country";
60

    
61
    private static final String MF_REFLOCALE_LANGUAGE = "reference-locale-language";
62

    
63
    private static final String I18N_EXTENSION = "org.gvsig.i18n";
64

    
65
    private static final String VARIANT = "variant";
66

    
67
    private static final String COUNTRY = "country";
68

    
69
    private static final String LANGUAGE = "language";
70

    
71
    private static final String REGISTERED_LOCALES_PERSISTENCE = "RegisteredLocales";
72

    
73
    private static final I18nManager DEFAULT = new I18nManagerImpl();
74

    
75
    private Set registeredLocales;
76

    
77
    /**
78
     * The list of default reference locales. The last one will be used to get
79
     * all the keys when translating to a new locale.
80
     */
81
    private Locale[] referenceLocales = new Locale[] { ENGLISH, SPANISH };
82

    
83
    private Locale[] defaultLocales = new Locale[] {
84
    // Default supported locales
85
            new Locale("ca"), // Catalan
86
            new Locale("cs"), // Czech
87
            new Locale("de"), // German
88
            ENGLISH, // English
89
            SPANISH, // Spanish
90
            new Locale("eu"), // Basque
91
            new Locale("fr"), // French
92
            new Locale("gl"), // Galician
93
            new Locale("it"), // Italian
94
            new Locale("pl"), // Polish
95
            new Locale("pt"), // Portuguese
96
            new Locale("ro"), // Romanian
97
            new Locale("zh"), // Chinese
98
    };
99

    
100
    /**
101
     * Returns the unique instance of the I18nManager.
102
     * 
103
     * @return the unique instance
104
     */
105
    public static I18nManager getInstance() {
106
        return DEFAULT;
107
    }
108

    
109
    public static String capitalize(String text) {
110
        // Convert the first letter to uppercase
111
        String capitalLetter = new String(new char[] { Character
112
                .toUpperCase(text.charAt(0)) });
113
        return capitalLetter.concat(text.substring(1));
114
    }
115

    
116
    /**
117
     * Empty constructor.
118
     */
119
    I18nManagerImpl() {
120
    }
121

    
122
    public Locale[] getInstalledLocales() {
123
        if (registeredLocales == null) {
124

    
125
            XMLEntity child = getRegisteredLocalesPersistence();
126

    
127
            // If the list of registered locales is not already persisted,
128
            // this should be the first time gvSIG is run with the I18nPlugin
129
            // so we will take the list of default locales
130
            if (child == null) {
131
                Locale[] defaultLocales = getDefaultLocales();
132
                registeredLocales = new HashSet(defaultLocales.length);
133
                for (int i = 0; i < defaultLocales.length; i++) {
134
                    registeredLocales.add(defaultLocales[i]);
135
                }
136
                storeInstalledLocales();
137
            }
138
            else {
139
                XMLEntity localesEntity = getRegisteredLocalesPersistence();
140
                registeredLocales = new HashSet(localesEntity
141
                        .getChildrenCount());
142
                for (int i = 0; i < localesEntity.getChildrenCount(); i++) {
143
                    XMLEntity localeEntity = localesEntity.getChild(i);
144
                    String language = localeEntity.getStringProperty(LANGUAGE);
145
                    String country = localeEntity.getStringProperty(COUNTRY);
146
                    String variant = localeEntity.getStringProperty(VARIANT);
147
                    Locale locale = new Locale(language, country, variant);
148
                    registeredLocales.add(locale);
149
                }
150
            }
151
        }
152

    
153
        return (Locale[]) registeredLocales
154
                .toArray(new Locale[registeredLocales.size()]);
155
    }
156

    
157
    public void uninstallLocale(Locale locale) throws I18nException {
158
        if (getCurrentLocale().equals(locale) || isReferenceLocale(locale)) {
159
            throw new UninstallLocaleException(locale);
160
        }
161

    
162
        if (registeredLocales.remove(locale)) {
163
            // Remove from the configured locale list
164
            storeInstalledLocales();
165

    
166
            // Remove the resource bundle file
167
            File bundleFile = new File(getResourcesFolder(),
168
                    getResourceFileName(locale));
169

    
170
            if (bundleFile.exists()) {
171
                bundleFile.delete();
172
            }
173
        }
174
    }
175

    
176
    public String getDisplayName(Locale locale) {
177
        return getDisplayName(locale, locale);
178
    }
179

    
180
    public String getDisplayName(Locale locale, Locale displayLocale) {
181
        StringBuffer name = new StringBuffer(getLanguageDisplayName(locale,
182
                displayLocale));
183

    
184
        boolean close = false;
185

    
186
        if (!isEmpty(locale.getCountry())) {
187
            name.append(" (");
188
            name.append(locale.getDisplayCountry(displayLocale));
189
            close = true;
190
        }
191

    
192
        if (!isEmpty(locale.getVariant())) {
193
            if (close) {
194
                name.append(" - ");
195
            }
196
            else {
197
                name.append(" (");
198
                close = true;
199
            }
200
            name.append(locale.getDisplayVariant(displayLocale));
201
        }
202

    
203
        if (close) {
204
            name.append(')');
205
        }
206

    
207
        return name.toString();
208
    }
209

    
210
    private boolean isEmpty(String text) {
211
        return text == null || text.trim().length() == 0;
212
    }
213

    
214
    public String getLanguageDisplayName(Locale locale) {
215
        return getLanguageDisplayName(locale, locale);
216
    }
217

    
218
    public String getLanguageDisplayName(Locale locale, Locale displayLocale) {
219

    
220
        String displayName;
221

    
222
        // Correction for the Basque language display name,
223
        // show it in Basque, not in Spanish
224
        if ("eu".equals(locale.getLanguage())
225
                && "vascuence".equals(locale.getDisplayLanguage())) {
226
            displayName = "Euskera";
227
        }
228
        // Patch for Valencian/Catalan
229
        else if ("ca".equals(locale.getLanguage())) {
230
            displayName = Messages.getText("__catalan");
231
        }
232
        else {
233
            displayName = locale.getDisplayLanguage(displayLocale);
234
        }
235

    
236
        return capitalize(displayName);
237
    }
238

    
239
    public Locale getCurrentLocale() {
240
        return Locale.getDefault();
241
    }
242

    
243
    public void setCurrentLocale(Locale locale) {
244
        AndamiConfig config = Launcher.getAndamiConfig();
245
        config.setLocaleLanguage(locale.getLanguage());
246
        config.setLocaleCountry(locale.getCountry());
247
        config.setLocaleVariant(locale.getVariant());
248
    }
249

    
250
    public Locale[] installLocales(File importFile) throws I18nException {
251
        List importLocales = new ArrayList();
252

    
253
        try {
254
            FileInputStream fis = new FileInputStream(importFile);
255
            JarInputStream jaris = new JarInputStream(fis);
256

    
257
            JarEntry entry;
258
            while ((entry = jaris.getNextJarEntry()) != null) {
259

    
260
                Attributes attributes = entry.getAttributes();
261

    
262
                if (attributes != null) {
263
                    // Extract the language only if it is not a reference
264
                    // language
265
                    String language = attributes.getValue(MF_LOCALE_LANGUAGE);
266
                    if (language != null) {
267
                        String country = attributes.getValue(MF_LOCALE_COUNTRY);
268
                        country = country == null ? "" : country;
269
                        String variant = attributes.getValue(MF_LOCALE_VARIANT);
270
                        variant = variant == null ? "" : variant;
271
                        Locale locale = new Locale(language, country, variant);
272
                        importLocales.add(locale);
273

    
274
                        // Add the locale to the list of installed ones, if it
275
                        // is
276
                        // new, otherwise, update the texts.
277
                        if (!registeredLocales.contains(locale)) {
278
                            registeredLocales.add(locale);
279
                            storeInstalledLocales();
280
                        }
281

    
282
                        // Replace the old bundle with the new one
283
                        File bundleFile = getResourceFile(locale);
284
                        FileOutputStream fos = new FileOutputStream(bundleFile);
285
                        BufferedOutputStream bos = new BufferedOutputStream(fos);
286
                        int len = 0;
287
                        byte[] buffer = new byte[2048];
288
                        while ((len = jaris.read(buffer)) > 0) {
289
                            bos.write(buffer, 0, len);
290
                        }
291
                        bos.flush();
292
                        bos.close();
293
                        fos.close();
294
                    }
295
                }
296
            }
297

    
298
        } catch (Exception ex) {
299
            throw new InstallLocalesException(importFile, ex);
300
        }
301

    
302
        return (Locale[]) importLocales
303
                .toArray(new Locale[importLocales.size()]);
304
    }
305

    
306
    public void exportLocaleForUpdate(Locale locale, Locale referenceLocale,
307
            File exportFile) throws I18nException {
308
        Manifest manifest = new Manifest();
309

    
310
        // First, add an entry for the locale to update
311
        addLocaleToManifest(locale, manifest, false);
312
        // Next, another entry for the reference locale, if not a default one
313
        if (!isReferenceLocale(referenceLocale)) {
314
            addLocaleToManifest(referenceLocale, manifest, true);
315
        }
316
        // Finally, an entry per default reference locale, if not the locale to
317
        // update
318
        for (int i = 0; i < referenceLocales.length; i++) {
319
            if (!locale.equals(referenceLocales[i])) {
320
                addLocaleToManifest(referenceLocales[i], manifest, true);
321
            }
322
        }
323

    
324
        try {
325
            FileOutputStream fos = new FileOutputStream(exportFile);
326
            JarOutputStream jaros = new JarOutputStream(fos, manifest);
327

    
328
            // BufferedOutputStream bos = new BufferedOutputStream(jaros);
329
            // PrintStream ps = new PrintStream(bos);
330
            PrintStream ps = new PrintStream(jaros);
331
            Map texts = null;
332

    
333
            // First, export the locale to update
334
            texts = getAllTexts(locale);
335
            putResourceInJar(jaros, ps, texts, getResourceFileName(locale));
336
            // Next, export the locale selected as reference (if not in the
337
            // default reference locales list)
338
            if (!isReferenceLocale(referenceLocale)) {
339
                texts = getAllTexts(referenceLocale);
340
                putResourceInJar(jaros, ps, texts,
341
                        getResourceFileName(referenceLocale));
342
            }
343
            // Finally, export the default reference locales
344
            for (int i = 0; i < referenceLocales.length; i++) {
345
                if (!locale.equals(referenceLocales[i])) {
346
                    texts = getAllTexts(referenceLocales[i]);
347
                    putResourceInJar(jaros, ps, texts,
348
                            getResourceFileName(referenceLocales[i]));
349
                }
350
            }
351

    
352
            ps.flush();
353
            ps.close();
354
            jaros.close();
355
            fos.close();
356
        } catch (IOException ex) {
357
            throw new ExportLocaleException(locale, ex);
358
        }
359
    }
360

    
361
    public void exportLocaleForTranslation(Locale locale,
362
            Locale referenceLocale, File exportFile) throws I18nException {
363
        Manifest manifest = new Manifest();
364

    
365
        // First, add an entry for the locale to update
366
        addLocaleToManifest(locale, manifest, false);
367
        // Next, another entry for the reference locale, if not a default one
368
        if (!isReferenceLocale(referenceLocale)) {
369
            addLocaleToManifest(referenceLocale, manifest, true);
370
        }
371
        // Finally, an entry per default reference locale
372
        for (int i = 0; i < referenceLocales.length; i++) {
373
            addLocaleToManifest(referenceLocales[i], manifest, true);
374
        }
375

    
376
        try {
377
            FileOutputStream fos = new FileOutputStream(exportFile);
378
            JarOutputStream jaros = new JarOutputStream(fos, manifest);
379

    
380
            // BufferedOutputStream bos = new BufferedOutputStream(jaros);
381
            // PrintStream ps = new PrintStream(bos);
382
            PrintStream ps = new PrintStream(jaros);
383
            Map texts = null;
384

    
385
            // First, export the reference locale translations
386
            for (int i = 0; i < referenceLocales.length; i++) {
387
                texts = getAllTexts(referenceLocales[i]);
388
                putResourceInJar(jaros, ps, texts,
389
                        getResourceFileName(referenceLocales[i]));
390
            }
391
            // Next, export the locale selected as reference (if not in the
392
            // default reference locales list)
393
            if (!isReferenceLocale(referenceLocale)) {
394
                texts = getAllTexts(referenceLocale);
395
                putResourceInJar(jaros, ps, texts,
396
                        getResourceFileName(referenceLocale));
397
            }
398

    
399
            // Finally, the new locale translations, taking the keys from
400
            // the reference locale, but without values
401
            // We will use the keys of the last reference locale
402
            putResourceInJar(jaros, ps, texts, getResourceFileName(locale),
403
                    false);
404

    
405
            ps.close();
406
            // bos.close();
407
            jaros.close();
408
            fos.close();
409
        } catch (IOException ex) {
410
            throw new ExportLocaleException(locale, ex);
411
        }
412
    }
413

    
414
    public Locale[] getReferenceLocales() {
415
        return referenceLocales;
416
    }
417

    
418
    public void setReferenceLocales(Locale[] referenceLocales) {
419
        this.referenceLocales = referenceLocales;
420
    }
421

    
422
    public void setDefaultLocales(Locale[] defaultLocales) {
423
        this.defaultLocales = defaultLocales;
424
    }
425

    
426
    /**
427
     * Returns all the localized texts and its keys for a locale.
428
     */
429
    private Map getAllTexts(Locale locale) {
430
        return Messages.getAllTexts(locale);
431
    }
432

    
433
    /**
434
     * Returns if a locale is one of the default reference ones.
435
     */
436
    private boolean isReferenceLocale(Locale locale) {
437
        for (int i = 0; i < referenceLocales.length; i++) {
438
            if (referenceLocales[i].equals(locale)) {
439
                return true;
440
            }
441
        }
442
        return false;
443
    }
444

    
445
    /**
446
     * Puts a new resource file into a Jar file.
447
     */
448
    private void putResourceInJar(JarOutputStream jaros, PrintStream ps,
449
            Map texts, String resourceFileName) throws IOException {
450

    
451
        putResourceInJar(jaros, ps, texts, resourceFileName, true);
452
    }
453

    
454
    /**
455
     * Puts a new resource file into a Jar file.
456
     */
457
    private void putResourceInJar(JarOutputStream jaros, PrintStream ps,
458
            Map texts, String resourceFileName, boolean withValue)
459
            throws IOException {
460
        // Add ZIP entry for the resource bundle file
461
        jaros.putNextEntry(new ZipEntry(resourceFileName));
462

    
463
        for (Iterator iterator = texts.entrySet().iterator(); iterator
464
                .hasNext();) {
465
            Entry entry = (Entry) iterator.next();
466
            String keyEncoded = escape((String) entry.getKey(), true);
467
            ps.print(keyEncoded);
468
            ps.print("=");
469
            if (withValue) {
470
                String valueEncoded = escape((String) entry.getValue(), false);
471
                ps.println(valueEncoded);
472
            }
473
            else {
474
                ps.println();
475
            }
476
        }
477

    
478
        ps.flush();
479

    
480
        // Close the ZIP entry, the file is complete
481
        jaros.closeEntry();
482
    }
483

    
484
    /**
485
     * Adds an entry to a MANIFEST.MF file with the name of a locale resource
486
     * bundle properties file, adding labels with the id of the locale, and if
487
     * it is a reference or the locale to translate or update.
488
     */
489
    private String addLocaleToManifest(Locale locale, Manifest manifest,
490
            boolean isReference) {
491
        final String languageKey = isReference ? MF_REFLOCALE_LANGUAGE
492
                : MF_LOCALE_LANGUAGE;
493
        final String countryKey = isReference ? MF_REFLOCALE_COUNTRY
494
                : MF_LOCALE_COUNTRY;
495
        final String variantKey = isReference ? MF_REFLOCALE_VARIANT
496
                : MF_LOCALE_VARIANT;
497

    
498
        String resourceFileName = getResourceFileName(locale);
499

    
500
        Attributes localeAttributes = new Attributes(4);
501
        localeAttributes.putValue(languageKey, locale.getLanguage());
502
        String country = locale.getCountry();
503
        if (country != null && country.length() > 0) {
504
            localeAttributes.putValue(countryKey, country);
505
        }
506
        String variant = locale.getVariant();
507
        if (variant != null && variant.length() > 0) {
508
            localeAttributes.putValue(variantKey, variant);
509
        }
510
        manifest.getEntries().put(resourceFileName, localeAttributes);
511

    
512
        return resourceFileName;
513
    }
514

    
515
    /**
516
     * Returns the file which contains the translations for a locale.
517
     */
518
    private File getResourceFile(Locale locale) {
519
        return new File(getResourcesFolder(), getResourceFileName(locale));
520
    }
521

    
522
    /**
523
     * Returns the name of the file which contains the translations for a
524
     * locale.
525
     */
526
    private String getResourceFileName(Locale locale) {
527
        StringBuffer fileName = new StringBuffer("text");
528

    
529
        // Spanish without country is the default locale
530
        if (!(isEmpty(locale.getCountry()) && "es".equals(locale.getLanguage()))) {
531
            fileName.append('_').append(locale.getLanguage());
532
        }
533

    
534
        // Add the locale country
535
        if (!isEmpty(locale.getCountry())) {
536
            fileName.append('_').append(locale.getCountry());
537
        }
538

    
539
        // Add the locale variant
540
        if (!isEmpty(locale.getVariant())) {
541
            fileName.append('_').append(locale.getVariant());
542
        }
543

    
544
        fileName.append(".properties");
545
        return fileName.toString();
546
    }
547

    
548
    /**
549
     * Returns the folder where to store the resource bundle files.
550
     */
551
    private File getResourcesFolder() {
552
        return PluginServices.getPluginServices("com.iver.cit.gvsig")
553
                .getPluginDirectory();
554
    }
555

    
556
    /**
557
     * Returns the child XMLEntity with the RegisteredLocales.
558
     */
559
    private XMLEntity getRegisteredLocalesPersistence() {
560
        XMLEntity entity = getI18nPersistence();
561
        XMLEntity child = null;
562
        for (int i = entity.getChildrenCount() - 1; i >= 0; i--) {
563
            XMLEntity tmpchild = entity.getChild(i);
564
            if (tmpchild.getName().equals(REGISTERED_LOCALES_PERSISTENCE)) {
565
                child = tmpchild;
566
                break;
567
            }
568
        }
569
        return child;
570
    }
571

    
572
    /**
573
     * Returns the I18n Plugin persistence.
574
     */
575
    private XMLEntity getI18nPersistence() {
576
        XMLEntity entity = PluginServices.getPluginServices(I18N_EXTENSION)
577
                .getPersistentXML();
578
        return entity;
579
    }
580

    
581
    /**
582
     * Returns the list of default locales bundled with gvSIG.
583
     */
584
    private Locale[] getDefaultLocales() {
585
        return defaultLocales;
586
    }
587

    
588
    /**
589
     * Stores the list of installed locales into the plugin persistence.
590
     */
591
    private void storeInstalledLocales() {
592
        XMLEntity localesEntity = getRegisteredLocalesPersistence();
593

    
594
        // Remove the previous list of registered languages
595
        if (localesEntity != null) {
596
            XMLEntity i18nPersistence = getI18nPersistence();
597
            for (int i = i18nPersistence.getChildrenCount() - 1; i >= 0; i--) {
598
                XMLEntity child = i18nPersistence.getChild(i);
599
                if (child.getName().equals(REGISTERED_LOCALES_PERSISTENCE)) {
600
                    i18nPersistence.removeChild(i);
601
                    break;
602
                }
603
            }
604
        }
605

    
606
        // Create the new persistence for the registered languages
607
        localesEntity = new XMLEntity();
608

    
609
        localesEntity.setName(REGISTERED_LOCALES_PERSISTENCE);
610

    
611
        for (Iterator iterator = registeredLocales.iterator(); iterator
612
                .hasNext();) {
613
            Locale locale = (Locale) iterator.next();
614
            XMLEntity localeEntity = new XMLEntity();
615
            localeEntity.setName(locale.getDisplayName());
616
            localeEntity.putProperty(LANGUAGE, locale.getLanguage());
617
            localeEntity.putProperty(COUNTRY, locale.getCountry());
618
            localeEntity.putProperty(VARIANT, locale.getVariant());
619

    
620
            localesEntity.addChild(localeEntity);
621
        }
622

    
623
        getI18nPersistence().addChild(localesEntity);
624
    }
625

    
626
    private String escape(String value, boolean replaceBlanks) {
627
        // First replace non printable characters
628
        if (replaceBlanks) {
629
            value = StringUtilities.replace(value, " ", "\\ ");
630
        }
631
        value = StringUtilities.replace(value, ":", "\\:");
632
        value = StringUtilities.replace(value, "\n", "\\n");
633
        value = StringUtilities.replace(value, "\t", "\\t");
634
        value = StringUtilities.replace(value, "\b", "\\b");
635
        value = StringUtilities.replace(value, "\f", "\\f");
636
        value = StringUtilities.replace(value, "\r", "\\r");
637
        // value = StringUtilities.replace(value, "\\", "\\\\");
638
        // value = StringUtilities.replace(value, "\'", "\\\'");
639
        // value = StringUtilities.replace(value, "\"", "\\\"");
640

    
641
        // Next, encode in raw-unicode-escape
642
        return toRawUnicodeEncoded(value);
643
    }
644

    
645
    private String toRawUnicodeEncoded(String value) {
646
        StringBuffer sb = new StringBuffer();
647
        for (int i = 0; i < value.length(); i++) {
648
            char c = value.charAt(i);
649
            if (c <= 0x80) {
650
                sb.append(c);
651
            }
652
            else {
653
                sb.append("\\u");
654
                String hexValue = Integer.toHexString((int) c);
655
                // Append 0 if the hex value has less than 4 digits
656
                for (int j = hexValue.length(); j < 4; j++) {
657
                    sb.append('0');
658
                }
659
                sb.append(hexValue);
660
            }
661
        }
662
        return sb.toString();
663
    }
664
}