1 /*
2 * Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> and others
3 *
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Distribution License v. 1.0 which is available at
6 * https://www.eclipse.org/org/documents/edl-v10.php.
7 *
8 * SPDX-License-Identifier: BSD-3-Clause
9 */
10
11 package org.eclipse.jgit.nls;
12
13 import java.lang.reflect.Field;
14 import java.util.Locale;
15 import java.util.MissingResourceException;
16 import java.util.ResourceBundle;
17
18 import org.eclipse.jgit.errors.TranslationBundleLoadingException;
19 import org.eclipse.jgit.errors.TranslationStringMissingException;
20
21 /**
22 * Base class for all translation bundles that provides injection of translated
23 * texts into public String fields.
24 *
25 * <p>
26 * The usage pattern is shown with the following example. First define a new
27 * translation bundle:
28 *
29 * <pre>
30 * public class TransportText extends TranslationBundle {
31 * public static TransportText get() {
32 * return NLS.getBundleFor(TransportText.class);
33 * }
34 *
35 * public String repositoryNotFound;
36 *
37 * public String transportError;
38 * }
39 * </pre>
40 *
41 * Second, define one or more resource bundle property files.
42 *
43 * <pre>
44 * TransportText_en_US.properties:
45 * repositoryNotFound=repository {0} not found
46 * transportError=unknown error talking to {0}
47 * TransportText_de.properties:
48 * repositoryNotFound=repository {0} nicht gefunden
49 * transportError=unbekannter Fehler während der Kommunikation mit {0}
50 * ...
51 * </pre>
52 *
53 * Then make use of it:
54 *
55 * <pre>
56 * NLS.setLocale(Locale.GERMAN); // or skip this call to stick to the JVM default locale
57 * ...
58 * throw new TransportException(uri, TransportText.get().transportError);
59 * </pre>
60 *
61 * The translated text is automatically injected into the public String fields
62 * according to the locale set with
63 * {@link org.eclipse.jgit.nls.NLS#setLocale(Locale)}. However, the
64 * {@link org.eclipse.jgit.nls.NLS#setLocale(Locale)} method defines only
65 * prefered locale which will be honored only if it is supported by the provided
66 * resource bundle property files. Basically, this class will use
67 * {@link java.util.ResourceBundle#getBundle(String, Locale)} method to load a
68 * resource bundle. See the documentation of this method for a detailed
69 * explanation of resource bundle loading strategy. After a bundle is created
70 * the {@link #effectiveLocale()} method can be used to determine whether the
71 * bundle really corresponds to the requested locale or is a fallback.
72 *
73 * <p>
74 * To load a String from a resource bundle property file this class uses the
75 * {@link java.util.ResourceBundle#getString(String)}. This method can throw the
76 * {@link java.util.MissingResourceException} and this class is not making any
77 * effort to catch and/or translate this exception.
78 *
79 * <p>
80 * To define a concrete translation bundle one has to:
81 * <ul>
82 * <li>extend this class
83 * <li>define a public static get() method like in the example above
84 * <li>define public static String fields for each text message
85 * <li>make sure the translation bundle class provide public no arg constructor
86 * <li>provide one or more resource bundle property files in the same package
87 * where the translation bundle class resides
88 * </ul>
89 */
90 public abstract class TranslationBundle {
91
92 private Locale effectiveLocale;
93 private ResourceBundle resourceBundle;
94
95 /**
96 * Get the locale used for loading the resource bundle from which the field
97 * values were taken.
98 *
99 * @return the locale used for loading the resource bundle from which the
100 * field values were taken.
101 */
102 public Locale effectiveLocale() {
103 return effectiveLocale;
104 }
105
106 /**
107 * Get the resource bundle on which this translation bundle is based.
108 *
109 * @return the resource bundle on which this translation bundle is based.
110 */
111 public ResourceBundle resourceBundle() {
112 return resourceBundle;
113 }
114
115 /**
116 * Injects locale specific text in all instance fields of this instance.
117 * Only public instance fields of type <code>String</code> are considered.
118 * <p>
119 * The name of this (sub)class plus the given <code>locale</code> parameter
120 * define the resource bundle to be loaded. In other words the
121 * <code>this.getClass().getName()</code> is used as the
122 * <code>baseName</code> parameter in the
123 * {@link ResourceBundle#getBundle(String, Locale)} parameter to load the
124 * resource bundle.
125 * <p>
126 *
127 * @param locale
128 * defines the locale to be used when loading the resource bundle
129 * @exception TranslationBundleLoadingException
130 * see {@link TranslationBundleLoadingException}
131 * @exception TranslationStringMissingException
132 * see {@link TranslationStringMissingException}
133 */
134 void load(Locale locale)
135 throws TranslationBundleLoadingException {
136 Class bundleClass = getClass();
137 try {
138 resourceBundle = ResourceBundle.getBundle(bundleClass.getName(),
139 locale, bundleClass.getClassLoader());
140 } catch (MissingResourceException e) {
141 throw new TranslationBundleLoadingException(bundleClass, locale, e);
142 }
143 this.effectiveLocale = resourceBundle.getLocale();
144
145 for (Field field : bundleClass.getFields()) {
146 if (field.getType().equals(String.class)) {
147 try {
148 String translatedText = resourceBundle.getString(field.getName());
149 field.set(this, translatedText);
150 } catch (MissingResourceException e) {
151 throw new TranslationStringMissingException(bundleClass, locale, field.getName(), e);
152 } catch (IllegalArgumentException | IllegalAccessException e) {
153 throw new Error(e);
154 }
155 }
156 }
157 }
158 }