001/* 002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining 005 * a copy of this software and associated documentation files (the 006 * "Software"), to deal in the Software without restriction, including 007 * without limitation the rights to use, copy, modify, merge, publish, 008 * distribute, sublicense, and/or sell copies of the Software, and to 009 * permit persons to whom the Software is furnished to do so, subject to 010 * the following conditions: 011 * 012 * The above copyright notice and this permission notice shall be 013 * included in all copies or substantial portions of the Software. 014 * 015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 016 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 017 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 018 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 019 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 020 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 021 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 022 */ 023 024package co.aikar.commands; 025 026import co.aikar.locales.LocaleManager; 027import co.aikar.locales.MessageKey; 028import co.aikar.locales.MessageKeyProvider; 029import com.google.common.collect.HashMultimap; 030import com.google.common.collect.SetMultimap; 031import com.google.common.collect.Sets; 032import org.jetbrains.annotations.NotNull; 033 034import java.util.ArrayList; 035import java.util.HashMap; 036import java.util.LinkedHashMap; 037import java.util.List; 038import java.util.Locale; 039import java.util.Map; 040import java.util.Set; 041import java.util.regex.Matcher; 042 043@SuppressWarnings("WeakerAccess") 044public class Locales { 045 // Locales for reference since Locale doesn't have as many, add our own here for ease of use. 046 public static final Locale ENGLISH = Locale.ENGLISH; 047 public static final Locale GERMAN = Locale.GERMAN; 048 public static final Locale FRENCH = Locale.FRENCH; 049 public static final Locale JAPANESE = Locale.JAPANESE; 050 public static final Locale ITALIAN = Locale.ITALIAN; 051 public static final Locale KOREAN = Locale.KOREAN; 052 public static final Locale CHINESE = Locale.CHINESE; 053 public static final Locale SIMPLIFIED_CHINESE = Locale.SIMPLIFIED_CHINESE; 054 public static final Locale TRADITIONAL_CHINESE = Locale.TRADITIONAL_CHINESE; 055 public static final Locale SPANISH = new Locale("es"); 056 public static final Locale DUTCH = new Locale("nl"); 057 public static final Locale DANISH = new Locale("da"); 058 public static final Locale CZECH = new Locale("cs"); 059 public static final Locale GREEK = new Locale("el"); 060 public static final Locale LATIN = new Locale("la"); 061 public static final Locale BULGARIAN = new Locale("bg"); 062 public static final Locale AFRIKAANS = new Locale("af"); 063 public static final Locale HINDI = new Locale("hi"); 064 public static final Locale HEBREW = new Locale("he"); 065 public static final Locale POLISH = new Locale("pl"); 066 public static final Locale PORTUGUESE = new Locale("pt"); 067 public static final Locale FINNISH = new Locale("fi"); 068 public static final Locale SWEDISH = new Locale("sv"); 069 public static final Locale RUSSIAN = new Locale("ru"); 070 public static final Locale ROMANIAN = new Locale("ro"); 071 public static final Locale VIETNAMESE = new Locale("vi"); 072 public static final Locale THAI = new Locale("th"); 073 public static final Locale TURKISH = new Locale("tr"); 074 public static final Locale UKRANIAN = new Locale("uk"); 075 public static final Locale ARABIC = new Locale("ar"); 076 public static final Locale WELSH = new Locale("cy"); 077 public static final Locale NORWEGIAN_BOKMAAL = new Locale("nb"); 078 public static final Locale NORWEGIAN_NYNORSK = new Locale("nn"); 079 080 private final CommandManager manager; 081 private final LocaleManager<CommandIssuer> localeManager; 082 private final Map<ClassLoader, SetMultimap<String, Locale>> loadedBundles = new HashMap<>(); 083 private final List<ClassLoader> registeredClassLoaders = new ArrayList<>(); 084 085 public Locales(CommandManager manager) { 086 this.manager = manager; 087 this.localeManager = LocaleManager.create(manager::getIssuerLocale); 088 this.addBundleClassLoader(this.getClass().getClassLoader()); 089 } 090 091 public void loadLanguages() { 092 addMessageBundles("acf-core"); 093 } 094 095 public Locale getDefaultLocale() { 096 return this.localeManager.getDefaultLocale(); 097 } 098 099 public Locale setDefaultLocale(Locale locale) { 100 return this.localeManager.setDefaultLocale(locale); 101 } 102 103 /** 104 * Looks for all previously loaded bundles, and if any new Supported Languages have been added, load them. 105 */ 106 public void loadMissingBundles() { 107 //noinspection unchecked 108 Set<Locale> supportedLanguages = manager.getSupportedLanguages(); 109 for (Locale locale : supportedLanguages) { 110 for(SetMultimap<String, Locale> localeData: this.loadedBundles.values()) { 111 for (String bundleName : Sets.newHashSet(localeData.keys())) { 112 addMessageBundle(bundleName, locale); 113 } 114 } 115 116 } 117 } 118 119 public void addMessageBundles(String... bundleNames) { 120 for (String bundleName : bundleNames) { 121 //noinspection unchecked 122 Set<Locale> supportedLanguages = manager.getSupportedLanguages(); 123 for (Locale locale : supportedLanguages) { 124 addMessageBundle(bundleName, locale); 125 } 126 } 127 } 128 129 public boolean addMessageBundle(String bundleName, Locale locale) { 130 boolean found = false; 131 for(ClassLoader classLoader: this.registeredClassLoaders) { 132 if(this.addMessageBundle(classLoader, bundleName, locale)) { 133 found = true; 134 } 135 } 136 137 return found; 138 } 139 140 public boolean addMessageBundle(ClassLoader classLoader, String bundleName, Locale locale) { 141 SetMultimap<String, Locale> classLoadersLocales = this.loadedBundles.getOrDefault(classLoader, HashMultimap.create()); 142 if(!classLoadersLocales.containsEntry(bundleName, locale)) { 143 if(this.localeManager.addMessageBundle(classLoader, bundleName, locale)) { 144 classLoadersLocales.put(bundleName, locale); 145 this.loadedBundles.put(classLoader, classLoadersLocales); 146 return true; 147 } 148 } 149 150 return false; 151 } 152 153 public void addMessageStrings(Locale locale, @NotNull Map<String, String> messages) { 154 Map<MessageKey, String> map = new HashMap<>(messages.size()); 155 messages.forEach((key, value) -> map.put(MessageKey.of(key), value)); 156 this.localeManager.addMessages(locale, map); 157 } 158 159 public void addMessages(Locale locale, @NotNull Map<? extends MessageKeyProvider, String> messages) { 160 Map<MessageKey, String> messagesMap = new LinkedHashMap<>(); 161 for (Map.Entry<? extends MessageKeyProvider, String> entry : messages.entrySet()) { 162 messagesMap.put(entry.getKey().getMessageKey(), entry.getValue()); 163 } 164 165 this.localeManager.addMessages(locale, messagesMap); 166 } 167 168 public String addMessage(Locale locale, MessageKeyProvider key, String message) { 169 return this.localeManager.addMessage(locale, key.getMessageKey(), message); 170 } 171 172 public String getMessage(CommandIssuer issuer, MessageKeyProvider key) { 173 final MessageKey msgKey = key.getMessageKey(); 174 String message = this.localeManager.getMessage(issuer, msgKey); 175 if (message == null) { 176 manager.log(LogLevel.ERROR, "Missing Language Key: " + msgKey.getKey()); 177 message = "<MISSING_LANGUAGE_KEY:" + msgKey.getKey() + ">"; 178 } 179 return message; 180 } 181 182 public String replaceI18NStrings(String message) { 183 if (message == null) { 184 return null; 185 } 186 Matcher matcher = ACFPatterns.I18N_STRING.matcher(message); 187 if (!matcher.matches()) { 188 return message; 189 } 190 191 CommandIssuer issuer = CommandManager.getCurrentCommandIssuer(); 192 193 matcher.reset(); 194 StringBuffer sb = new StringBuffer(message.length()); 195 while (matcher.find()) { 196 MessageKey key = MessageKey.of(matcher.group("key")); 197 matcher.appendReplacement(sb, Matcher.quoteReplacement(getMessage(issuer, key))); 198 } 199 matcher.appendTail(sb); 200 return sb.toString(); 201 } 202 203 public boolean addBundleClassLoader(ClassLoader classLoader) { 204 return !this.registeredClassLoaders.contains(classLoader) && this.registeredClassLoaders.add(classLoader); 205 206 } 207}