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}