com.squarespace.cldrengine.api.LocaleMatcher Maven / Gradle / Ivy
The newest version!
package com.squarespace.cldrengine.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import com.google.gson.JsonObject;
import com.squarespace.cldrengine.internal.LocaleExternalData;
import com.squarespace.cldrengine.locale.CLocaleImpl;
import com.squarespace.cldrengine.locale.DistanceTable;
import com.squarespace.cldrengine.locale.LanguageTagParser;
import com.squarespace.cldrengine.locale.LocaleMatch;
import com.squarespace.cldrengine.locale.LocaleResolver;
import com.squarespace.cldrengine.utils.JsonUtils;
import lombok.ToString;
public class LocaleMatcher {
private static final Map PARADIGM_LOCALES = load();
private static final Pattern TAG_SEP = Pattern.compile("[,\\s]+", Pattern.MULTILINE);
private static final LanguageTag UNDEFINED = new LanguageTag();
private final Map> exactMap = new HashMap<>();
private final List supported;
private Entry defaultEntry;
private int count;
public LocaleMatcher(String supportedLocales) {
this.supported = parse(supportedLocales);
init();
}
public LocaleMatcher(String[] supportedLocales) {
this.supported = parse(supportedLocales);
init();
}
public LocaleMatcher(List supportedLocales) {
this.supported = parse(supportedLocales);
init();
}
/**
* Find the desired locale that is the closed match to a supported locale, within
* the given threshold. Any matches whose distance is greater than or equal to the
* threshold will be treated as having maximum distance.
*/
public LocaleMatch match(String desiredLocales) {
return match(desiredLocales, DistanceTable.DEFAULT_THRESHOLD);
}
/**
* Find the desired locale that is the closed match to a supported locale, within
* the given threshold. Any matches whose distance is greater than or equal to the
* threshold will be treated as having maximum distance.
*/
public LocaleMatch match(String desiredLocales, int threshold) {
List desireds = parse(desiredLocales);
return this._match(desireds, threshold);
}
/**
* Find the desired locale that is the closed match to a supported locale, within
* the given threshold. Any matches whose distance is greater than or equal to the
* threshold will be treated as having maximum distance.
*/
public LocaleMatch match(String[] desiredLocales) {
return match(desiredLocales, DistanceTable.DEFAULT_THRESHOLD);
}
/**
* Find the desired locale that is the closed match to a supported locale, within
* the given threshold. Any matches whose distance is greater than or equal to the
* threshold will be treated as having maximum distance.
*/
public LocaleMatch match(String[] desiredLocales, int threshold) {
List desireds = parse(desiredLocales);
return this._match(desireds, threshold);
}
/**
* Find the desired locale that is the closed match to a supported locale, within
* the given threshold. Any matches whose distance is greater than or equal to the
* threshold will be treated as having maximum distance.
*/
public LocaleMatch match(List desiredLocales) {
return match(desiredLocales, DistanceTable.DEFAULT_THRESHOLD);
}
/**
* Find the desired locale that is the closed match to a supported locale, within
* the given threshold. Any matches whose distance is greater than or equal to the
* threshold will be treated as having maximum distance.
*/
public LocaleMatch match(List desiredLocales, int threshold) {
List desireds = parse(desiredLocales);
return this._match(desireds, threshold);
}
private LocaleMatch _match(List desireds, int threshold) {
int len = desireds.size();
int bestDistance = DistanceTable.MAX_DISTANCE;
Entry bestMatch = null;
Entry bestDesired = len == 0 ? this.defaultEntry : desireds.get(0);
for (int i = 0; i < len; i++) {
Entry desired = desireds.get(i);
List exact = this.exactMap.get(desired.compact);
if (exact != null) {
CLocale locale = new CLocaleImpl(exact.get(0).id, exact.get(0).tag);
return new LocaleMatch(locale, 0);
}
for (int j = 0; j < this.count; j++) {
Entry supported = this.supported.get(j);
int distance = DistanceTable.distance(desired.tag, supported.tag, threshold);
if (distance < bestDistance) {
bestDistance = distance;
bestMatch = supported;
bestDesired = desired;
}
}
}
if (bestMatch == null) {
bestMatch = this.defaultEntry;
}
LanguageTag tag = new LanguageTag(
bestMatch.tag.language(),
bestMatch.tag.script(),
bestMatch.tag.region(),
bestMatch.tag.variant(),
bestDesired.tag.extensions(),
bestDesired.tag.privateUse(),
bestDesired.tag.extlangs()
);
CLocale locale = new CLocaleImpl(bestMatch.id, tag);
return new LocaleMatch(locale, bestDistance);
}
private void init() {
this.count = this.supported.size();
// The first locale in the list is used as the default.
this.defaultEntry = this.supported.get(0);
this.supported.sort((Entry a, Entry b) -> {
// Keep default tag at the front..
if (a.tag == this.defaultEntry.tag) {
return -1;
}
if (b.tag == this.defaultEntry.tag) {
return 1;
}
// Sort all paradigm locales before non-paradigms.
Integer pa = PARADIGM_LOCALES.get(a.compact);
Integer pb = PARADIGM_LOCALES.get(b.compact);
if (pa != null) {
return pb == null ? -1 : Integer.compare(pa, pb);
} else if (pb != null) {
return 1;
}
// All other locales stay in their relative positions.
return 0;
});
// Wire up a map for quick lookups of exact matches. These have a
// distance of 0 and will short-circuit the matching loop.
this.supported.forEach(locale -> {;
String key = locale.compact;
List bundles = this.exactMap.get(key);
if (bundles == null) {
bundles = new ArrayList<>();
this.exactMap.put(key, bundles);
}
bundles.add(locale);
});
}
private static List parse(String locales) {
List res = new ArrayList<>();
for (String locale : TAG_SEP.split(locales)) {
res.add(parseEntry(locale));
}
return res;
}
private static List parse(List locales) {
List res = new ArrayList<>();
for (String locale : locales) {
res.addAll(parse(locale));
}
return res;
}
private static List parse(String[] locales) {
List res = new ArrayList<>();
for (String locale : locales) {
res.addAll(parse(locale));
}
return res;
}
private static Entry parseEntry(String raw) {
String id = raw.trim();
LanguageTag tag = LanguageTagParser.parse(id);
if (tag.hasLanguage() || tag.hasScript() || tag.hasRegion()) {
return new Entry(id, LocaleResolver.resolve(tag));
}
return new Entry(id, UNDEFINED);
}
@ToString
static class Entry {
private final String id;
private final LanguageTag tag;
private final String compact;
public Entry(String id, LanguageTag tag) {
this.id = id;
this.tag = tag;
this.compact = tag.compact();
}
}
private static final Map load() {
JsonObject elems = JsonUtils.parse(LocaleExternalData.PARADIGMLOCALES).getAsJsonObject();
Map res = new HashMap<>();
for (String raw : elems.keySet()) {
int i = elems.get(raw).getAsInt();
String compact = LocaleResolver.resolve(raw).compact();
res.put(compact, i);
}
return res;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy