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 }