diff --git a/utils/pom.xml b/utils/pom.xml index b9423ce1..2d45311b 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -44,6 +44,10 @@ + + com.google.code.findbugs + jsr305 + jakarta.annotation jakarta.annotation-api diff --git a/utils/src/main/java/org/killbill/commons/utils/locale/LocaleUtils.java b/utils/src/main/java/org/killbill/commons/utils/locale/LocaleUtils.java new file mode 100644 index 00000000..518480d6 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/locale/LocaleUtils.java @@ -0,0 +1,373 @@ +/* + * Copyright 2020-2026 Equinix, Inc + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.locale; + +import java.util.Locale; +import java.util.Map; + +import javax.annotation.Nullable; + +public class LocaleUtils { + private LocaleUtils() { + } + + public static String localeString(final Locale locale, @Nullable final String prefix) { + return localeString(locale.getLanguage(), locale.getCountry(), prefix); + } + + public static String localeString(final String language, @Nullable final String prefix) { + return localeString(language, "", prefix); + } + + private static String localeString(final String language, + final String country, + @Nullable final String prefix) { + final StringBuilder tmp = new StringBuilder(); + if (prefix != null) { + tmp.append(prefix); + if (!prefix.endsWith("_")) { + tmp.append("_"); + } + } + + tmp.append(language); + if(!country.isEmpty()) { + tmp.append("_") + .append(country); + } + return tmp.toString(); + } + + // From commons-lang + public static Locale toLocale(final String str) { + if (str == null) { + return null; + } + final int len = str.length(); + if (len != 2 && len != 5 && len < 7) { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + final char ch0 = str.charAt(0); + final char ch1 = str.charAt(1); + if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + if (len == 2) { + return new Locale(str, ""); + } else { + if (str.charAt(2) != '_') { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + final char ch3 = str.charAt(3); + if (ch3 == '_') { + return new Locale(str.substring(0, 2), "", str.substring(4)); + } + final char ch4 = str.charAt(4); + if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + if (len == 5) { + return new Locale(str.substring(0, 2), str.substring(3, 5)); + } else { + if (str.charAt(5) != '_') { + throw new IllegalArgumentException("Invalid locale format: " + str); + } + return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6)); + } + } + } + + private static final Map COUNTRY_LOCALES = Map.ofEntries( + Map.entry("AF", new Locale("ps", "AF")), + Map.entry("AL", new Locale("sq", "AL")), + Map.entry("DZ", new Locale("ar", "DZ")), + Map.entry("AS", new Locale("en", "AS")), + Map.entry("AD", new Locale("ca", "AD")), + Map.entry("AO", new Locale("pt", "AO")), + Map.entry("AI", new Locale("en", "AI")), + Map.entry("AQ", new Locale("en", "AQ")), + Map.entry("AG", new Locale("en", "AG")), + Map.entry("AR", new Locale("es", "AR")), + Map.entry("AM", new Locale("hy", "AM")), + Map.entry("AW", new Locale("nl", "AW")), + Map.entry("AU", new Locale("en", "AU")), + Map.entry("AT", new Locale("de", "AT")), + Map.entry("AZ", new Locale("az", "AZ")), + + Map.entry("BS", new Locale("en", "BS")), + Map.entry("BH", new Locale("ar", "BH")), + Map.entry("BD", new Locale("bn", "BD")), + Map.entry("BB", new Locale("en", "BB")), + Map.entry("BY", new Locale("be", "BY")), + Map.entry("BE", new Locale("nl", "BE")), + Map.entry("BZ", new Locale("en", "BZ")), + Map.entry("BJ", new Locale("fr", "BJ")), + Map.entry("BM", new Locale("en", "BM")), + Map.entry("BT", new Locale("dz", "BT")), + Map.entry("BO", new Locale("es", "BO")), + Map.entry("BQ", new Locale("nl", "BQ")), + Map.entry("BA", new Locale("bs", "BA")), + Map.entry("BW", new Locale("en", "BW")), + Map.entry("BV", new Locale("no", "BV")), + Map.entry("BR", new Locale("pt", "BR")), + Map.entry("IO", new Locale("en", "IO")), + Map.entry("BN", new Locale("ms", "BN")), + Map.entry("BG", new Locale("bg", "BG")), + Map.entry("BF", new Locale("fr", "BF")), + Map.entry("BI", new Locale("fr", "BI")), + + Map.entry("CV", new Locale("pt", "CV")), + Map.entry("KH", new Locale("km", "KH")), + Map.entry("CM", new Locale("fr", "CM")), + Map.entry("CA", Locale.CANADA), + Map.entry("KY", new Locale("en", "KY")), + Map.entry("CF", new Locale("fr", "CF")), + Map.entry("TD", new Locale("fr", "TD")), + Map.entry("CL", new Locale("es", "CL")), + Map.entry("CN", Locale.CHINA), + Map.entry("CX", new Locale("en", "CX")), + Map.entry("CC", new Locale("en", "CC")), + Map.entry("CO", new Locale("es", "CO")), + Map.entry("KM", new Locale("ar", "KM")), + Map.entry("CG", new Locale("fr", "CG")), + Map.entry("CD", new Locale("fr", "CD")), + Map.entry("CK", new Locale("en", "CK")), + Map.entry("CR", new Locale("es", "CR")), + Map.entry("CI", new Locale("fr", "CI")), + Map.entry("HR", new Locale("hr", "HR")), + Map.entry("CU", new Locale("es", "CU")), + Map.entry("CW", new Locale("nl", "CW")), + Map.entry("CY", new Locale("el", "CY")), + Map.entry("CZ", new Locale("cs", "CZ")), + + Map.entry("DK", new Locale("da", "DK")), + Map.entry("DJ", new Locale("fr", "DJ")), + Map.entry("DM", new Locale("en", "DM")), + Map.entry("DO", new Locale("es", "DO")), + + Map.entry("EC", new Locale("es", "EC")), + Map.entry("EG", new Locale("ar", "EG")), + Map.entry("SV", new Locale("es", "SV")), + Map.entry("GQ", new Locale("es", "GQ")), + Map.entry("ER", new Locale("ti", "ER")), + Map.entry("EE", new Locale("et", "EE")), + Map.entry("SZ", new Locale("en", "SZ")), + Map.entry("ET", new Locale("am", "ET")), + + Map.entry("FK", new Locale("en", "FK")), + Map.entry("FO", new Locale("fo", "FO")), + Map.entry("FJ", new Locale("en", "FJ")), + Map.entry("FI", new Locale("fi", "FI")), + Map.entry("FR", Locale.FRANCE), + Map.entry("GF", new Locale("fr", "GF")), + Map.entry("PF", new Locale("fr", "PF")), + Map.entry("TF", new Locale("fr", "TF")), + + Map.entry("GA", new Locale("fr", "GA")), + Map.entry("GM", new Locale("en", "GM")), + Map.entry("GE", new Locale("ka", "GE")), + Map.entry("DE", Locale.GERMANY), + Map.entry("GH", new Locale("en", "GH")), + Map.entry("GI", new Locale("en", "GI")), + Map.entry("GR", new Locale("el", "GR")), + Map.entry("GL", new Locale("kl", "GL")), + Map.entry("GD", new Locale("en", "GD")), + Map.entry("GP", new Locale("fr", "GP")), + Map.entry("GU", new Locale("en", "GU")), + Map.entry("GT", new Locale("es", "GT")), + Map.entry("GG", new Locale("en", "GG")), + Map.entry("GN", new Locale("fr", "GN")), + Map.entry("GW", new Locale("pt", "GW")), + Map.entry("GY", new Locale("en", "GY")), + + Map.entry("HT", new Locale("fr", "HT")), + Map.entry("HM", new Locale("en", "HM")), + Map.entry("VA", new Locale("it", "VA")), + Map.entry("HN", new Locale("es", "HN")), + Map.entry("HK", new Locale("zh", "HK")), + Map.entry("HU", new Locale("hu", "HU")), + + Map.entry("IS", new Locale("is", "IS")), + Map.entry("IN", new Locale("en", "IN")), + Map.entry("ID", new Locale("id", "ID")), + Map.entry("IR", new Locale("fa", "IR")), + Map.entry("IQ", new Locale("ar", "IQ")), + Map.entry("IE", new Locale("en", "IE")), + Map.entry("IM", new Locale("en", "IM")), + Map.entry("IL", new Locale("he", "IL")), + Map.entry("IT", Locale.ITALY), + + Map.entry("JM", new Locale("en", "JM")), + Map.entry("JP", Locale.JAPAN), + Map.entry("JE", new Locale("en", "JE")), + Map.entry("JO", new Locale("ar", "JO")), + + Map.entry("KZ", new Locale("kk", "KZ")), + Map.entry("KE", new Locale("en", "KE")), + Map.entry("KI", new Locale("en", "KI")), + Map.entry("KP", new Locale("ko", "KP")), + Map.entry("KR", Locale.KOREA), + Map.entry("KW", new Locale("ar", "KW")), + Map.entry("KG", new Locale("ky", "KG")), + + Map.entry("LA", new Locale("lo", "LA")), + Map.entry("LV", new Locale("lv", "LV")), + Map.entry("LB", new Locale("ar", "LB")), + Map.entry("LS", new Locale("en", "LS")), + Map.entry("LR", new Locale("en", "LR")), + Map.entry("LY", new Locale("ar", "LY")), + Map.entry("LI", new Locale("de", "LI")), + Map.entry("LT", new Locale("lt", "LT")), + Map.entry("LU", new Locale("fr", "LU")), + + Map.entry("MO", new Locale("zh", "MO")), + Map.entry("MG", new Locale("mg", "MG")), + Map.entry("MW", new Locale("en", "MW")), + Map.entry("MY", new Locale("ms", "MY")), + Map.entry("MV", new Locale("dv", "MV")), + Map.entry("ML", new Locale("fr", "ML")), + Map.entry("MT", new Locale("mt", "MT")), + Map.entry("MH", new Locale("en", "MH")), + Map.entry("MQ", new Locale("fr", "MQ")), + Map.entry("MR", new Locale("ar", "MR")), + Map.entry("MU", new Locale("en", "MU")), + Map.entry("YT", new Locale("fr", "YT")), + Map.entry("MX", new Locale("es", "MX")), + Map.entry("FM", new Locale("en", "FM")), + Map.entry("MD", new Locale("ro", "MD")), + Map.entry("MC", new Locale("fr", "MC")), + Map.entry("MN", new Locale("mn", "MN")), + Map.entry("ME", new Locale("sr", "ME")), + Map.entry("MS", new Locale("en", "MS")), + Map.entry("MA", new Locale("ar", "MA")), + Map.entry("MZ", new Locale("pt", "MZ")), + Map.entry("MM", new Locale("my", "MM")), + + Map.entry("NA", new Locale("en", "NA")), + Map.entry("NR", new Locale("en", "NR")), + Map.entry("NP", new Locale("ne", "NP")), + Map.entry("NL", new Locale("nl", "NL")), + Map.entry("NC", new Locale("fr", "NC")), + Map.entry("NZ", new Locale("en", "NZ")), + Map.entry("NI", new Locale("es", "NI")), + Map.entry("NE", new Locale("fr", "NE")), + Map.entry("NG", new Locale("en", "NG")), + Map.entry("NU", new Locale("en", "NU")), + Map.entry("NF", new Locale("en", "NF")), + Map.entry("MK", new Locale("mk", "MK")), + Map.entry("MP", new Locale("en", "MP")), + Map.entry("NO", new Locale("no", "NO")), + + Map.entry("OM", new Locale("ar", "OM")), + + Map.entry("PK", new Locale("en", "PK")), + Map.entry("PW", new Locale("en", "PW")), + Map.entry("PS", new Locale("ar", "PS")), + Map.entry("PA", new Locale("es", "PA")), + Map.entry("PG", new Locale("en", "PG")), + Map.entry("PY", new Locale("es", "PY")), + Map.entry("PE", new Locale("es", "PE")), + Map.entry("PH", new Locale("en", "PH")), + Map.entry("PN", new Locale("en", "PN")), + Map.entry("PL", new Locale("pl", "PL")), + Map.entry("PT", new Locale("pt", "PT")), + Map.entry("PR", new Locale("es", "PR")), + + Map.entry("QA", new Locale("ar", "QA")), + + Map.entry("RE", new Locale("fr", "RE")), + Map.entry("RO", new Locale("ro", "RO")), + Map.entry("RU", new Locale("ru", "RU")), + Map.entry("RW", new Locale("rw", "RW")), + + Map.entry("BL", new Locale("fr", "BL")), + Map.entry("SH", new Locale("en", "SH")), + Map.entry("KN", new Locale("en", "KN")), + Map.entry("LC", new Locale("en", "LC")), + Map.entry("MF", new Locale("fr", "MF")), + Map.entry("PM", new Locale("fr", "PM")), + Map.entry("VC", new Locale("en", "VC")), + Map.entry("WS", new Locale("sm", "WS")), + Map.entry("SM", new Locale("it", "SM")), + Map.entry("ST", new Locale("pt", "ST")), + Map.entry("SA", new Locale("ar", "SA")), + Map.entry("SN", new Locale("fr", "SN")), + Map.entry("RS", new Locale("sr", "RS")), + Map.entry("SC", new Locale("en", "SC")), + Map.entry("SL", new Locale("en", "SL")), + Map.entry("SG", new Locale("en", "SG")), + Map.entry("SX", new Locale("nl", "SX")), + Map.entry("SK", new Locale("sk", "SK")), + Map.entry("SI", new Locale("sl", "SI")), + Map.entry("SB", new Locale("en", "SB")), + Map.entry("SO", new Locale("so", "SO")), + Map.entry("ZA", new Locale("en", "ZA")), + Map.entry("GS", new Locale("en", "GS")), + Map.entry("SS", new Locale("en", "SS")), + Map.entry("ES", new Locale("es", "ES")), + Map.entry("LK", new Locale("si", "LK")), + Map.entry("SD", new Locale("ar", "SD")), + Map.entry("SR", new Locale("nl", "SR")), + Map.entry("SJ", new Locale("no", "SJ")), + Map.entry("SE", new Locale("sv", "SE")), + Map.entry("CH", new Locale("de", "CH")), + Map.entry("SY", new Locale("ar", "SY")), + + Map.entry("TW", Locale.TAIWAN), + Map.entry("TJ", new Locale("tg", "TJ")), + Map.entry("TZ", new Locale("sw", "TZ")), + Map.entry("TH", new Locale("th", "TH")), + Map.entry("TL", new Locale("pt", "TL")), + Map.entry("TG", new Locale("fr", "TG")), + Map.entry("TK", new Locale("en", "TK")), + Map.entry("TO", new Locale("en", "TO")), + Map.entry("TT", new Locale("en", "TT")), + Map.entry("TN", new Locale("ar", "TN")), + Map.entry("TR", new Locale("tr", "TR")), + Map.entry("TM", new Locale("tk", "TM")), + Map.entry("TC", new Locale("en", "TC")), + Map.entry("TV", new Locale("en", "TV")), + + Map.entry("UG", new Locale("en", "UG")), + Map.entry("UA", new Locale("uk", "UA")), + Map.entry("AE", new Locale("ar", "AE")), + Map.entry("GB", Locale.UK), + Map.entry("US", Locale.US), + Map.entry("UM", new Locale("en", "UM")), + Map.entry("UY", new Locale("es", "UY")), + Map.entry("UZ", new Locale("uz", "UZ")), + + Map.entry("VU", new Locale("bi", "VU")), + Map.entry("VE", new Locale("es", "VE")), + Map.entry("VN", new Locale("vi", "VN")), + Map.entry("VG", new Locale("en", "VG")), + Map.entry("VI", new Locale("en", "VI")), + + Map.entry("WF", new Locale("fr", "WF")), + Map.entry("EH", new Locale("ar", "EH")), + + Map.entry("YE", new Locale("ar", "YE")), + + Map.entry("ZM", new Locale("en", "ZM")), + Map.entry("ZW", new Locale("en", "ZW")) + ); + + public static Locale toLocaleFromCountryCode(final String countryCode) { + return COUNTRY_LOCALES.get(countryCode); + } +}