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