/** * Gets the locale to use if one was not explicitly provided. * * @return the implicit locale to use, not null */ @Nonnull protected Locale getImplicitLocale() { Locale locale = null; if (getLocaleSupplier().isPresent()) { locale = getLocaleSupplier().get().get(); } else if (getLanguageRangesSupplier().isPresent()) { List<LanguageRange> languageRanges = getLanguageRangesSupplier().get().get(); if (languageRanges != null) locale = Locale.lookup(languageRanges, getLocalizedStringsByLocale().keySet()); } return locale == null ? getFallbackLocale() : locale; }
private static void testMapEquivalents() { String ranges = "HI-IN"; List<LanguageRange> priorityList = LanguageRange.parse(ranges); HashMap<String, List<String>> map = new LinkedHashMap<>(); List<String> equivalentList = new ArrayList<>(); equivalentList.add("HI"); equivalentList.add("HI-Deva"); map.put("HI", equivalentList); List<LanguageRange> expected = new ArrayList<>(); expected.add(new LanguageRange("hi-in")); expected.add(new LanguageRange("hi-deva-in")); List<LanguageRange> got = LanguageRange.mapEquivalents(priorityList, map); if (!areEqual(expected, got)) { System.err.println("testMapEquivalents() failed"); err = true; } }
private static boolean areEqual(List<LanguageRange> expected, List<LanguageRange> got) { boolean error = false; if (expected.equals(got)) { return !error; } List<LanguageRange> cloneExpected = new ArrayList<>(expected); cloneExpected.removeAll(got); if (!cloneExpected.isEmpty()) { error = true; System.err.println("Found missing range(s): " + cloneExpected); } // not creating the 'got' clone as the list will not be used after this got.removeAll(expected); if (!got.isEmpty()) { error = true; System.err.println("Found extra range(s): " + got); } return !error; }
private static void test_filterTags() { boolean error = false; String ranges = "gti;q=0.2, gfx"; String tags = "de-DE, gti, he, nyc, mwj, vaj"; List<LanguageRange> priorityList = LanguageRange.parse(ranges); List<String> tagList = generateLanguageTags(tags); String actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList)); String expectedTags = "mwj, vaj, gti, nyc"; if (!expectedTags.equals(actualTags)) { error = true; showErrorMessage("filterTags()", ranges, tags, expectedTags, actualTags); } if (error) { err = true; System.out.println(" test_filterTags() failed."); } else { System.out.println(" test_filterTags() passed."); } }
private static void test_lookup() { boolean error = false; String ranges = "en;q=0.2, yam, rmx;q=0.9"; String tags = "de-DE, en, kwq, lmm"; List<LanguageRange> priorityList = LanguageRange.parse(ranges); List<Locale> localeList = generateLocales(tags); String actualLocale = Locale.lookup(priorityList, localeList).toLanguageTag(); String expectedLocale = "kwq"; if (!expectedLocale.equals(actualLocale)) { error = true; showErrorMessage("lookup()", ranges, tags, expectedLocale, actualLocale); } if (error) { err = true; System.out.println(" test_lookup() failed."); } else { System.out.println(" test_lookup() passed."); } }
private static void test_lookupTag() { boolean error = false; String ranges = "en, tsf;q=0.2"; String tags = "es, ja-JP, taj"; List<LanguageRange> priorityList = LanguageRange.parse(ranges); List<String> tagList = generateLanguageTags(tags); String actualTag = Locale.lookupTag(priorityList, tagList); String expectedTag = "taj"; if (!expectedTag.equals(actualTag)) { error = true; showErrorMessage("lookupTag()", ranges, tags, expectedTag, actualTag); } if (error) { err = true; System.out.println(" test_lookupTag() failed."); } else { System.out.println(" test_lookupTag() passed."); } }
private static void testParse() { String ranges = "HI-Deva, ja-hIrA-JP, RKI"; try { LanguageRange.parse(ranges); } catch (Exception ex) { System.err.println("[testParse() failed on range string: " + ranges + "] due to "+ex); err = true; } }
private static void testFilter(FilteringMode mode) { String ranges = "hi-IN, itc-Ital"; String tags = "hi-IN, itc-Ital"; List<LanguageRange> priorityList = LanguageRange.parse(ranges); List<Locale> tagList = generateLocales(tags); String actualLocales = showLocales(Locale.filter(priorityList, tagList, mode)); String expectedLocales = "hi-IN, itc-Ital"; if (!expectedLocales.equals(actualLocales)) { System.err.println("testFilter(" + mode + ") failed on language ranges:" + " [" + ranges + "] and language tags: [" + tags + "]"); err = true; } }
private static void testLookup() { boolean error = false; String ranges = "hi-IN, itc-Ital"; String tags = "hi-IN, itc-Ital"; List<LanguageRange> priorityList = LanguageRange.parse(ranges); List<Locale> localeList = generateLocales(tags); Locale actualLocale = Locale.lookup(priorityList, localeList); String actualLocaleString = ""; if (actualLocale != null) { actualLocaleString = actualLocale.toLanguageTag(); } else { error = true; } String expectedLocale = "hi-IN"; if (!expectedLocale.equals(actualLocaleString)) { error = true; } if (error) { System.err.println("testLookup() failed on language ranges:" + " [" + ranges + "] and language tags: [" + tags + "]"); err = true; } }
public static void main(String[] args) { LanguageRange lr1 = new LanguageRange("ja", 1.0); LanguageRange lr2 = new LanguageRange("fr", 0.0); if (!lr1.toString().equals("ja") || !lr2.toString().equals("fr;q=0.0")) { throw new RuntimeException("LanguageRange.toString() returned an unexpected result."); } }
public static void testFilter(String ranges, List<String> tags, List<String> expected, FilteringMode mode) { List<LanguageRange> priorityList = LanguageRange.parse(ranges); List<String> actual = Locale.filterTags(priorityList, tags, mode); if (!actual.equals(expected)) { throw new RuntimeException("[filterTags() failed for the language" + " range: " + ranges + ", Expected: " + expected + ", Found: " + actual + "]"); } }
public static void testLookup(String ranges, List<String> tags, String expected) { List<LanguageRange> priorityList = LanguageRange.parse(ranges); String actual = Locale.lookupTag(priorityList, tags); if (!actual.equals(expected)) { throw new RuntimeException("[lookupTag() failed for the language" + " range: " + ranges + ", Expected: " + expected + ", Found: " + actual + "]"); } }
/** * Extract preferred language from HTTP "Accept-Language" header. * * @param acceptLanguageHeader * @param defaultValue * @return preferred language or <code>defaultValue</code> * @see https://tools.ietf.org/html/rfc5646#section-2.1 */ public String getAD_LanguageFromHttpAcceptLanguage(final String acceptLanguageHeader, final String defaultValue) { if (Check.isEmpty(acceptLanguageHeader, true)) { return defaultValue; } try { final List<LanguageRange> languageRanges = LanguageRange.parse(acceptLanguageHeader); if (languageRanges.isEmpty()) { return defaultValue; } final BiMap<String, String> adLanguage2tag = Services.get(ILanguageDAO.class).retrieveAvailableLanguages().toHttpLanguageTags(); final String languageTag = Locale.lookupTag(languageRanges, adLanguage2tag.values()); if (languageTag == null) { return defaultValue; } final String adLanguage = adLanguage2tag.inverse().get(languageTag.toLowerCase()); if (adLanguage == null) { return defaultValue; } return adLanguage; } catch (final Exception ex) { logger.warn("Failed fetching AD_Language from {}. Returning {}", acceptLanguageHeader, defaultValue, ex); return defaultValue; } }
private static Locale getLocale(String userLocale) { final List<LanguageRange> ranges = Locale.LanguageRange.parse(userLocale); if (ranges != null) { for (LanguageRange languageRange : ranges) { final String localeString = languageRange.getRange(); final Locale locale = Locale.forLanguageTag(localeString); return locale; } } return null; }
@Override public Locale resolve(RoutingContext context, List<Locale> availableLocales) { String accept = context.request().getHeader(ACCEPT_LANGUAGE.toString()); if (accept == null) { return null; } List<LanguageRange> ranges = LanguageRange.parse(accept); if (ranges.isEmpty()) { return null; } return Locale.lookup(ranges, availableLocales); }
@Override public Locale locale(final BiFunction<List<LanguageRange>, List<Locale>, Locale> filter) { Supplier<Locale> def = () -> filter.apply(ImmutableList.of(), locales); // don't fail on bad Accept-Language header, just fallback to default locale. return lang.map(h -> Try.apply(() -> filter.apply(LocaleUtils.range(h), locales)).orElseGet(def)) .orElseGet(def); }
@Test public void locales() throws Exception { new MockUnit(Route.class) .run(unit -> { assertEquals(null, new RequestMock() { @Override public List<Locale> locales( final BiFunction<List<LanguageRange>, List<Locale>, List<Locale>> filter) { return null; } }.locales()); }); }
/** * Constructs a localized string provider with builder-supplied data. * <p> * The fallback language code must be an ISO 639 alpha-2 or alpha-3 language code. * When a language has both an alpha-2 code and an alpha-3 code, the alpha-2 code must be used. * * @param fallbackLanguageCode fallback language code, not null * @param localizedStringSupplier supplier of localized strings, not null * @param localeSupplier locale supplier, may be null * @param languageRangesSupplier language ranges supplier, may be null * @param failureMode strategy for dealing with lookup failures, may be null */ protected DefaultStrings(@Nonnull String fallbackLanguageCode, @Nonnull Supplier<Map<Locale, ? extends Iterable<LocalizedString>>> localizedStringSupplier, @Nullable Supplier<Locale> localeSupplier, @Nullable Supplier<List<LanguageRange>> languageRangesSupplier, @Nullable FailureMode failureMode) { requireNonNull(fallbackLanguageCode); requireNonNull(localizedStringSupplier); this.logger = Logger.getLogger(LoggerType.STRINGS.getLoggerName()); Map<Locale, ? extends Iterable<LocalizedString>> suppliedLocalizedStringsByLocale = localizedStringSupplier.get(); if (suppliedLocalizedStringsByLocale == null) suppliedLocalizedStringsByLocale = Collections.emptyMap(); // Defensive copy of iterator to unmodifiable set Map<Locale, Set<LocalizedString>> localizedStringsByLocale = suppliedLocalizedStringsByLocale.entrySet().stream() .collect(Collectors.toMap( entry -> entry.getKey(), entry -> { Set<LocalizedString> localizedStrings = new LinkedHashSet<>(); entry.getValue().forEach(localizedStrings::add); return Collections.unmodifiableSet(localizedStrings); } )); this.fallbackLocale = Locale.forLanguageTag(fallbackLanguageCode); this.fallbackLanguageCode = fallbackLanguageCode; this.localizedStringsByLocale = Collections.unmodifiableMap(localizedStringsByLocale); this.languageRangesSupplier = languageRangesSupplier; this.failureMode = failureMode == null ? FailureMode.USE_FALLBACK : failureMode; this.stringInterpolator = new StringInterpolator(); this.expressionEvaluator = new ExpressionEvaluator(); this.localizedStringsByKeyByLocale = Collections.unmodifiableMap(localizedStringsByLocale.entrySet().stream() .collect(Collectors.toMap( entry1 -> entry1.getKey(), entry1 -> Collections.unmodifiableMap(entry1.getValue().stream() .collect(Collectors.toMap( entry2 -> entry2.getKey(), entry2 -> entry2 ) ))))); this.localizedStringSourcesByLocale = new ConcurrentHashMap<>(); if (!localizedStringsByLocale.containsKey(getFallbackLocale())) throw new IllegalArgumentException(format("Specified fallback language code is '%s' but no matching " + "localized strings locale was found. Known locales: [%s]", fallbackLanguageCode, localizedStringsByLocale.keySet().stream() .map(locale -> locale.toLanguageTag()) .sorted() .collect(Collectors.joining(", ")))); if (localeSupplier != null && languageRangesSupplier != null) throw new IllegalArgumentException(format("You cannot provide both a localeSupplier " + "and a languageRangesSupplier when building an instance of %s - you must pick one of the two.", getClass().getSimpleName())); if (localeSupplier == null && languageRangesSupplier == null) this.localeSupplier = () -> getFallbackLocale(); else this.localeSupplier = localeSupplier; }
private static void test_parse() { boolean error = false; String str = "Accept-Language: aam, adp, aue, ema, en-gb-oed," + " gti, koj, kwq, kxe, lii, lmm, mtm, ngv, oyb, phr, pub," + " suj, taj;q=0.9, yug;q=0.5, gfx;q=0.4"; ArrayList<LanguageRange> expected = new ArrayList<>(); expected.add(new LanguageRange("aam", 1.0)); expected.add(new LanguageRange("aas", 1.0)); expected.add(new LanguageRange("adp", 1.0)); expected.add(new LanguageRange("dz", 1.0)); expected.add(new LanguageRange("aue", 1.0)); expected.add(new LanguageRange("ktz", 1.0)); expected.add(new LanguageRange("ema", 1.0)); expected.add(new LanguageRange("uok", 1.0)); expected.add(new LanguageRange("en-gb-oed", 1.0)); expected.add(new LanguageRange("en-gb-oxendict", 1.0)); expected.add(new LanguageRange("gti", 1.0)); expected.add(new LanguageRange("nyc", 1.0)); expected.add(new LanguageRange("koj", 1.0)); expected.add(new LanguageRange("kwv", 1.0)); expected.add(new LanguageRange("kwq", 1.0)); expected.add(new LanguageRange("yam", 1.0)); expected.add(new LanguageRange("kxe", 1.0)); expected.add(new LanguageRange("tvd", 1.0)); expected.add(new LanguageRange("lii", 1.0)); expected.add(new LanguageRange("raq", 1.0)); expected.add(new LanguageRange("lmm", 1.0)); expected.add(new LanguageRange("rmx", 1.0)); expected.add(new LanguageRange("mtm", 1.0)); expected.add(new LanguageRange("ymt", 1.0)); expected.add(new LanguageRange("ngv", 1.0)); expected.add(new LanguageRange("nnx", 1.0)); expected.add(new LanguageRange("oyb", 1.0)); expected.add(new LanguageRange("thx", 1.0)); expected.add(new LanguageRange("phr", 1.0)); expected.add(new LanguageRange("pmu", 1.0)); expected.add(new LanguageRange("pub", 1.0)); expected.add(new LanguageRange("puz", 1.0)); expected.add(new LanguageRange("suj", 1.0)); expected.add(new LanguageRange("xsj", 1.0)); expected.add(new LanguageRange("taj", 0.9)); expected.add(new LanguageRange("tsf", 0.9)); expected.add(new LanguageRange("yug", 0.5)); expected.add(new LanguageRange("yuu", 0.5)); expected.add(new LanguageRange("gfx", 0.4)); expected.add(new LanguageRange("oun", 0.4)); expected.add(new LanguageRange("mwj", 0.4)); expected.add(new LanguageRange("vaj", 0.4)); List<LanguageRange> got = LanguageRange.parse(str); if (!areEqual(expected, got)) { error = true; System.err.println(" language parse() test failed."); } if (error) { err = true; System.err.println(" test_parse() failed."); } else { System.out.println(" test_parse() passed."); } }
private static void test_filter() { boolean error = false; String ranges = "mtm-RU, en-gb-oed"; String tags = "de-DE, en, mtm-RU, ymt-RU, en-gb-oxendict, ja-JP"; FilteringMode mode = EXTENDED_FILTERING; List<LanguageRange> priorityList = LanguageRange.parse(ranges); List<Locale> tagList = generateLocales(tags); String actualLocales = showLocales(Locale.filter(priorityList, tagList, mode)); String expectedLocales = "mtm-RU, ymt-RU, en-GB-oxendict"; if (!expectedLocales.equals(actualLocales)) { error = true; showErrorMessage("#1 filter(" + mode + ")", ranges, tags, expectedLocales, actualLocales); } ranges = "phr-*-IN, ja-JP"; tags = "en, pmu-Guru-IN, ja-Latn-JP, iw"; mode = EXTENDED_FILTERING; priorityList = LanguageRange.parse(ranges); tagList = generateLocales(tags); actualLocales = showLocales(Locale.filter(priorityList, tagList, mode)); expectedLocales = "pmu-Guru-IN, ja-Latn-JP"; if (!expectedLocales.equals(actualLocales)) { error = true; showErrorMessage("#2 filter(" + mode + ")", ranges, tags, expectedLocales, actualLocales); } if (error) { err = true; System.out.println(" test_filter() failed."); } else { System.out.println(" test_filter() passed."); } }
@Override public List<Locale> locales( final BiFunction<List<Locale.LanguageRange>, List<Locale>, List<Locale>> filter) { return lang.map(h -> filter.apply(LocaleUtils.range(h), locales)) .orElseGet(() -> filter.apply(ImmutableList.of(), locales)); }
@Override public Locale locale(final BiFunction<List<LanguageRange>, List<Locale>, Locale> filter) { return req.locale(filter); }
@Override public List<Locale> locales( final BiFunction<List<LanguageRange>, List<Locale>, List<Locale>> filter) { return req.locales(filter); }
@Override public List<Locale> locales( final BiFunction<List<LanguageRange>, List<Locale>, List<Locale>> filter) { throw new UnsupportedOperationException(); }
@Override public Locale locale(final BiFunction<List<LanguageRange>, List<Locale>, Locale> filter) { throw new UnsupportedOperationException(); }
/** * Gets the language ranges supplier. * * @return the language ranges supplier, not null */ @Nonnull protected Optional<Supplier<List<LanguageRange>>> getLanguageRangesSupplier() { return Optional.ofNullable(languageRangesSupplier); }
@Test public void languageRange() { Strings strings = new DefaultStrings.Builder("en", () -> LocalizedStringLoader.loadFromClasspath("strings") ).languageRangesSupplier(() -> LanguageRange.parse("en-US;q=1.0,en-GB;q=0.5,fr-FR;q=0.25")).build(); String translation = strings.get("I am going on vacation"); Assert.assertEquals("I am going on vacation", translation); Strings enGbStrings = new DefaultStrings.Builder("en", () -> LocalizedStringLoader.loadFromClasspath("strings") ).languageRangesSupplier(() -> LanguageRange.parse("en-GB;q=1.0,en;q=0.75,en-US;q=0.5,fr-FR;q=0.25")).build(); String enGbTranslation = enGbStrings.get("I am going on vacation"); Assert.assertEquals("I am going on holiday", enGbTranslation); Strings enUsStrings = new DefaultStrings.Builder("ru", () -> LocalizedStringLoader.loadFromClasspath("strings") ).languageRangesSupplier(() -> LanguageRange.parse("en-US;q=1.0,en-GB;q=0.5,fr-FR;q=0.25")).build(); String enUsTranslation = enUsStrings.get("I am going on vacation"); Assert.assertEquals("I am going on vacation", enUsTranslation); Strings ruStrings = new DefaultStrings.Builder("ru", () -> LocalizedStringLoader.loadFromClasspath("strings") ).languageRangesSupplier(() -> LanguageRange.parse("fr;q=1.0,ru;q=0.25")).build(); String ruTranslation = ruStrings.get("I am going on vacation - MISSING KEY"); Assert.assertEquals("I am going on vacation - MISSING KEY", ruTranslation); Strings ru2Strings = new DefaultStrings.Builder("en", () -> LocalizedStringLoader.loadFromClasspath("strings") ).languageRangesSupplier(() -> LanguageRange.parse("fr;q=1.0,ru;q=0.25")).build(); String ru2Translation = ru2Strings.get("Hello, world!"); Assert.assertEquals("Приветствую, мир", ru2Translation); }
/** * Get a list of locale that best matches the current request. * * The first filter argument is the value of <code>Accept-Language</code> as * {@link Locale.LanguageRange} and filter while the second argument is a list of supported * locales defined by the <code>application.lang</code> property. * * The next example returns a list of matching {@code Locale} instances using the filtering * mechanism defined in RFC 4647: * * <pre>{@code * req.locales(Locale::filter) * }</pre> * * @param filter A locale filter. * @return A list of matching locales. */ @Nonnull List<Locale> locales(BiFunction<List<Locale.LanguageRange>, List<Locale>, List<Locale>> filter);
/** * Get a locale that best matches the current request. * * The first filter argument is the value of <code>Accept-Language</code> as * {@link Locale.LanguageRange} and filter while the second argument is a list of supported * locales defined by the <code>application.lang</code> property. * * The next example returns a {@code Locale} instance for the best-matching language * tag using the lookup mechanism defined in RFC 4647. * * <pre>{@code * req.locale(Locale::lookup) * }</pre> * * @param filter A locale filter. * @return A matching locale. */ @Nonnull Locale locale(BiFunction<List<Locale.LanguageRange>, List<Locale>, Locale> filter);