001package net.filebot; 002 003import static java.util.Arrays.*; 004import static java.util.Collections.*; 005import static java.util.Comparator.*; 006import static java.util.stream.Collectors.*; 007import static net.filebot.Logging.*; 008import static net.filebot.util.RegularExpressions.*; 009 010import java.io.Serializable; 011import java.util.Comparator; 012import java.util.List; 013import java.util.Locale; 014import java.util.ResourceBundle; 015import java.util.stream.Stream; 016 017public class Language implements Serializable { 018 019 // ISO 639-1 code 020 private final String iso_639_1; 021 022 // ISO 639-3 code (mostly identical to ISO 639-2/T) 023 private final String iso_639_3; 024 025 // ISO 639-2/B code 026 private final String iso_639_2B; 027 028 // BCP 47 language tag 029 private final String tag; 030 031 // Language name 032 private final String[] names; 033 034 public Language(String iso_639_1, String iso_639_3, String iso_639_2B, String tag, String[] names) { 035 this.iso_639_1 = iso_639_1; 036 this.iso_639_3 = iso_639_3; 037 this.iso_639_2B = iso_639_2B; 038 this.tag = tag; 039 this.names = names.clone(); 040 } 041 042 public String getCode() { 043 return iso_639_1; 044 } 045 046 public String getISO2() { 047 return iso_639_1; 048 } 049 050 public String getISO3() { 051 return iso_639_3; // 3-letter code 052 } 053 054 public String getISO3B() { 055 return iso_639_2B; // alternative 3-letter code 056 } 057 058 public String getTag() { 059 return tag; 060 } 061 062 public String getName() { 063 return names[0]; 064 } 065 066 public List<String> getNames() { 067 return unmodifiableList(asList(names)); 068 } 069 070 @Override 071 public String toString() { 072 return iso_639_3; 073 } 074 075 public Locale getLocale() { 076 Locale locale = Locale.forLanguageTag(tag); 077 078 // e.g. x-jat 079 if (locale == null || locale.getLanguage().isEmpty()) { 080 return new Locale(iso_639_1); 081 } 082 083 return locale; 084 } 085 086 public boolean matches(Language language) { 087 if (language == null) { 088 return false; 089 } 090 return getCode().equals(language.getCode()); 091 } 092 093 public boolean matches(String code) { 094 if (code == null || code.isEmpty()) { 095 return false; 096 } 097 return Stream.of(iso_639_1, iso_639_2B, iso_639_3, tag).anyMatch(code::equalsIgnoreCase) || stream(names).anyMatch(code::equalsIgnoreCase); 098 } 099 100 @Override 101 public Language clone() { 102 return new Language(iso_639_1, iso_639_3, iso_639_2B, tag, names); 103 } 104 105 public static final Comparator<Language> ALPHABETIC_ORDER = comparing(Language::getName, String::compareToIgnoreCase); 106 107 public static Language getLanguage(String code) { 108 if (code == null || code.isEmpty()) { 109 return null; 110 } 111 112 try { 113 String[] values = TAB.split(getProperty(code), 4); 114 return new Language(code, values[0], values[1], values[2], TAB.split(values[3])); 115 } catch (Exception e) { 116 debug.warning(message("Unexpected language code", code)); 117 } 118 119 try { 120 Locale locale = new Locale(code); 121 return new Language(code, locale.getISO3Language(), locale.getISO3Language(), locale.toLanguageTag(), new String[] { locale.getDisplayLanguage(Locale.ENGLISH) }); 122 } catch (Exception e) { 123 debug.warning(message("Invalid language code", code)); 124 } 125 126 return new Language(code, code, code, code, new String[] { code }); 127 } 128 129 public static List<Language> getLanguages(String... codes) { 130 return stream(codes).map(Language::getLanguage).collect(toList()); 131 } 132 133 public static Language getLanguage(Locale locale) { 134 return locale == null ? null : findLanguage(locale.getLanguage()); 135 } 136 137 public static Language findLanguage(String name) { 138 return availableLanguages().stream().filter(l -> l.matches(name)).findFirst().orElse(null); 139 } 140 141 public static Language forName(String name) { 142 Language language = Language.findLanguage(name); 143 if (language != null) { 144 return language; 145 } 146 throw new IllegalArgumentException(name + " not in " + availableLanguages()); 147 } 148 149 public static List<Language> availableLanguages() { 150 String languages = getProperty("languages.ui"); 151 return getLanguages(SPACE.split(languages)); 152 } 153 154 public static List<Language> commonLanguages() { 155 String languages = getProperty("languages.common"); 156 return getLanguages(SPACE.split(languages)); 157 } 158 159 public static List<Language> preferredLanguages() { 160 // English | system language | common languages 161 Stream<String> codes = Stream.of(Locale.ENGLISH, Locale.getDefault()).map(Locale::getLanguage); 162 163 // append common languages 164 codes = Stream.concat(codes, SPACE.splitAsStream(getProperty("languages.common"))).distinct(); 165 166 return codes.map(Language::getLanguage).collect(toList()); 167 } 168 169 public static Language defaultLanguage() { 170 return Language.getLanguage("en"); 171 } 172 173 private static String getProperty(String key) { 174 return ResourceBundle.getBundle(Language.class.getName()).getString(key); 175 } 176 177}