001package fr.aumgn.bukkitutils.localization; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.IOException; 006import java.io.InputStream; 007import java.io.InputStreamReader; 008import java.io.Reader; 009import java.net.URL; 010import java.net.URLConnection; 011import java.text.MessageFormat; 012import java.util.ArrayDeque; 013import java.util.Deque; 014import java.util.HashMap; 015import java.util.Iterator; 016import java.util.LinkedHashSet; 017import java.util.Locale; 018import java.util.Map; 019import java.util.Set; 020 021import org.bukkit.plugin.java.JavaPlugin; 022 023import com.google.common.base.Charsets; 024 025import fr.aumgn.bukkitutils.localization.loaders.JsonMessagesLoader; 026import fr.aumgn.bukkitutils.localization.loaders.MessagesLoader; 027import fr.aumgn.bukkitutils.localization.loaders.PropertiesMessagesLoader; 028import fr.aumgn.bukkitutils.localization.loaders.YamlMessagesLoader; 029 030/** 031 * Class which handles loading of localization 032 * resources for the given plugin and locale. 033 * 034 * This class looks for resources in the plugin's jarfile 035 * (in the plugin's package) and in the plugin's data folder. 036 * 037 * Resources are defined by a name to which is appended 038 * a suffix corresponding to the locale. 039 * 040 * For example if you're looking for a resource with the 041 * name "messages" for the plugin MyPlugin whose main class 042 * is "fr.aumgn.myplugin.MyPlugin" and the locale "fr_FR", 043 * the class will go through all this steps : 044 * <ul> 045 * <li> 046 * Looks for the resource 047 * fr/aumgn/myplugin/messages_en.{json,yml,properties} 048 * in the jarfile 049 * </li> 050 * <li> 051 * Looks for the resource 052 * [server]/plugins/MyPlugin/messages_en.{json,yml,properties} 053 * </li> 054 * <li> 055 * Looks for the resource 056 * fr/aumgn/myplugin/messages_en_US.{json,yml,properties} 057 * in the jarfile 058 * </li> 059 * <li> 060 * Looks for the resource 061 * [server]/plugins/MyPlugin/messages_en_US.{json,yml,properties} 062 * </li> 063 * <li> 064 * Looks for the resource 065 * fr/aumgn/myplugin/messages_fr.{json,yml,properties} 066 * in the jarfile 067 * </li> 068 * <li> 069 * Looks for the resource 070 * [server]/plugins/MyPlugin/messages_fr.{json,yml,properties} 071 * </li> 072 * <li> 073 * Looks for the resource 074 * fr/aumgn/myplugin/messages_fr_FR.{json,yml,properties} 075 * in the jarfile 076 * </li> 077 * <li> 078 * Looks for the resource 079 * [server]/plugins/MyPlugin/messages_fr_FR.{json,yml,properties} 080 * </li> 081 * </ul> 082 * 083 * Each values find in a loaded resources has priority 084 * over values find in previous resources. 085 */ 086public class Localization { 087 088 public static final Locale DEFAULT_LOCALE = Locale.US; 089 090 private static final Deque<MessagesLoader> loaders = 091 new ArrayDeque<MessagesLoader>(); 092 093 /** 094 * Registers a MessagesLoader. 095 */ 096 public static void register(MessagesLoader loader) { 097 loaders.push(loader); 098 } 099 100 protected static Iterable<MessagesLoader> loaders() { 101 return new Iterable<MessagesLoader>() { 102 @Override 103 public Iterator<MessagesLoader> iterator() { 104 return loaders.descendingIterator(); 105 } 106 }; 107 } 108 109 static { 110 register(new PropertiesMessagesLoader()); 111 register(new YamlMessagesLoader()); 112 register(new JsonMessagesLoader()); 113 } 114 115 public static Locale[] localesLookupFor(Locale targetLocale, 116 Locale fallbackLocale) { 117 Set<Locale> localesSet = new LinkedHashSet<Locale>(4); 118 localesSet.add(targetLocale); 119 localesSet.add(new Locale(targetLocale.getLanguage())); 120 localesSet.add(fallbackLocale); 121 localesSet.add(new Locale(fallbackLocale.getLanguage())); 122 Locale[] locales = new Locale[localesSet.size()]; 123 int i = locales.length - 1; 124 for (Locale locale : localesSet) { 125 locales[i] = locale; 126 i--; 127 } 128 return locales; 129 } 130 131 protected final JavaPlugin plugin; 132 private final File dir; 133 protected final Locale[] locales; 134 135 public Localization(JavaPlugin plugin) { 136 this(plugin, DEFAULT_LOCALE); 137 } 138 139 public Localization(JavaPlugin plugin, Locale targetLocale) { 140 this(plugin, plugin.getDataFolder(), targetLocale); 141 } 142 143 public Localization(JavaPlugin plugin, Locale targetLocale, 144 Locale fallbackLocale) { 145 this(plugin, plugin.getDataFolder(), targetLocale, fallbackLocale); 146 } 147 148 public Localization(JavaPlugin plugin, File dir, Locale targetLocale) { 149 this(plugin, dir, targetLocale, DEFAULT_LOCALE); 150 } 151 152 public Localization(JavaPlugin plugin, File dir, Locale targetLocale, 153 Locale fallbackLocale) { 154 this.plugin = plugin; 155 this.dir = dir; 156 this.locales = localesLookupFor(targetLocale, fallbackLocale); 157 } 158 159 /** 160 * Loads the resources with the given name. 161 */ 162 public PluginMessages get(String name) { 163 return new PluginMessages(loadMap(name)); 164 } 165 166 protected Map<String, MessageFormat> loadMap(String name) { 167 Map<String, MessageFormat> map = new HashMap<String, MessageFormat>(); 168 for (Locale locale : locales) { 169 load(map, locale, name + "_" + locale.toString()); 170 } 171 return map; 172 } 173 174 protected void load(Map<String, MessageFormat> map, Locale locale, 175 String name) { 176 loadBundled(map, locale, name); 177 loadUser(map, locale, name); 178 } 179 180 protected void loadStream(Map<String, MessageFormat> map, Locale locale, 181 MessagesLoader loader, InputStream iStream) { 182 Reader reader = 183 new InputStreamReader(iStream, Charsets.UTF_8); 184 map.putAll(loader.load(locale, reader)); 185 try { 186 reader.close(); 187 } catch (IOException exc) { 188 } 189 } 190 191 private void loadBundled(Map<String, MessageFormat> map, Locale locale, 192 String baseName) { 193 if (plugin == null) { 194 return; 195 } 196 197 for (MessagesLoader loader : loaders()) { 198 for (String extension : loader.getExtensions()) { 199 String name = baseName + "." + extension; 200 201 InputStream iStream = null; 202 URL res = plugin.getClass().getResource(name); 203 if (res != null) { 204 try { 205 URLConnection connection = res.openConnection(); 206 connection.setUseCaches(false); 207 iStream = connection.getInputStream(); 208 } catch (IOException _) { 209 } 210 } 211 212 if (iStream != null) { 213 loadStream(map, locale, loader, iStream); 214 return; 215 } 216 } 217 } 218 } 219 220 private void loadUser(Map<String, MessageFormat> map, Locale locale, 221 String baseName) { 222 if (dir == null) { 223 return; 224 } 225 226 for (MessagesLoader loader : loaders()) { 227 for (String extension : loader.getExtensions()) { 228 String name = baseName + "." + extension; 229 230 File file = new File(dir, name); 231 if (file.exists()) { 232 try { 233 InputStream iStream = new FileInputStream(file); 234 loadStream(map, locale, loader, iStream); 235 } catch (IOException exc) { 236 } 237 } 238 } 239 } 240 } 241}