mirror of https://github.com/mpv-player/mpv
363 lines
7.8 KiB
C
363 lines
7.8 KiB
C
/*
|
|
* Language code utility functions
|
|
*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* mpv is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "language.h"
|
|
|
|
#include "common/common.h"
|
|
#include "osdep/strnlen.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
static const struct lang {
|
|
char match[4];
|
|
char canonical[4];
|
|
} langmap[] = {
|
|
{"aa", "aar"},
|
|
{"ab", "abk"},
|
|
{"ae", "ave"},
|
|
{"af", "afr"},
|
|
{"ak", "aka"},
|
|
{"am", "amh"},
|
|
{"an", "arg"},
|
|
{"ar", "ara"},
|
|
{"as", "asm"},
|
|
{"av", "ava"},
|
|
{"ay", "aym"},
|
|
{"az", "aze"},
|
|
{"ba", "bak"},
|
|
{"be", "bel"},
|
|
{"bg", "bul"},
|
|
{"bh", "bih"},
|
|
{"bi", "bis"},
|
|
{"bm", "bam"},
|
|
{"bn", "ben"},
|
|
{"bo", "tib"},
|
|
{"bod", "tib"},
|
|
{"br", "bre"},
|
|
{"bs", "bos"},
|
|
{"ca", "cat"},
|
|
{"ce", "che"},
|
|
{"ces", "cze"},
|
|
{"ch", "cha"},
|
|
{"co", "cos"},
|
|
{"cr", "cre"},
|
|
{"cs", "cze"},
|
|
{"cu", "chu"},
|
|
{"cv", "chv"},
|
|
{"cy", "wel"},
|
|
{"cym", "wel"},
|
|
{"da", "dan"},
|
|
{"de", "ger"},
|
|
{"deu", "ger"},
|
|
{"dv", "div"},
|
|
{"dz", "dzo"},
|
|
{"ee", "ewe"},
|
|
{"el", "gre"},
|
|
{"ell", "gre"},
|
|
{"en", "eng"},
|
|
{"eo", "epo"},
|
|
{"es", "spa"},
|
|
{"et", "est"},
|
|
{"eu", "baq"},
|
|
{"eus", "baq"},
|
|
{"fa", "per"},
|
|
{"fas", "per"},
|
|
{"ff", "ful"},
|
|
{"fi", "fin"},
|
|
{"fj", "fij"},
|
|
{"fo", "fao"},
|
|
{"fr", "fre"},
|
|
{"fra", "fre"},
|
|
{"fy", "fry"},
|
|
{"ga", "gle"},
|
|
{"gd", "gla"},
|
|
{"gl", "glg"},
|
|
{"gn", "grn"},
|
|
{"gu", "guj"},
|
|
{"gv", "glv"},
|
|
{"ha", "hau"},
|
|
{"he", "heb"},
|
|
{"hi", "hin"},
|
|
{"ho", "hmo"},
|
|
{"hr", "hrv"},
|
|
{"ht", "hat"},
|
|
{"hu", "hun"},
|
|
{"hy", "arm"},
|
|
{"hye", "arm"},
|
|
{"hz", "her"},
|
|
{"ia", "ina"},
|
|
{"id", "ind"},
|
|
{"ie", "ile"},
|
|
{"ig", "ibo"},
|
|
{"ii", "iii"},
|
|
{"ik", "ipk"},
|
|
{"io", "ido"},
|
|
{"is", "ice"},
|
|
{"isl", "ice"},
|
|
{"it", "ita"},
|
|
{"iu", "iku"},
|
|
{"ja", "jpn"},
|
|
{"jv", "jav"},
|
|
{"ka", "geo"},
|
|
{"kat", "geo"},
|
|
{"kg", "kon"},
|
|
{"ki", "kik"},
|
|
{"kj", "kua"},
|
|
{"kk", "kaz"},
|
|
{"kl", "kal"},
|
|
{"km", "khm"},
|
|
{"kn", "kan"},
|
|
{"ko", "kor"},
|
|
{"kr", "kau"},
|
|
{"ks", "kas"},
|
|
{"ku", "kur"},
|
|
{"kv", "kom"},
|
|
{"kw", "cor"},
|
|
{"ky", "kir"},
|
|
{"la", "lat"},
|
|
{"lb", "ltz"},
|
|
{"lg", "lug"},
|
|
{"li", "lim"},
|
|
{"ln", "lin"},
|
|
{"lo", "lao"},
|
|
{"lt", "lit"},
|
|
{"lu", "lub"},
|
|
{"lv", "lav"},
|
|
{"mg", "mlg"},
|
|
{"mh", "mah"},
|
|
{"mi", "mao"},
|
|
{"mk", "mac"},
|
|
{"mkd", "mac"},
|
|
{"ml", "mal"},
|
|
{"mn", "mon"},
|
|
{"mr", "mar"},
|
|
{"mri", "mao"},
|
|
{"ms", "may"},
|
|
{"msa", "may"},
|
|
{"mt", "mlt"},
|
|
{"my", "bur"},
|
|
{"mya", "bur"},
|
|
{"na", "nau"},
|
|
{"nb", "nob"},
|
|
{"nd", "nde"},
|
|
{"ne", "nep"},
|
|
{"ng", "ndo"},
|
|
{"nl", "dut"},
|
|
{"nld", "dut"},
|
|
{"nn", "nno"},
|
|
{"no", "nor"},
|
|
{"nr", "nbl"},
|
|
{"nv", "nav"},
|
|
{"ny", "nya"},
|
|
{"oc", "oci"},
|
|
{"oj", "oji"},
|
|
{"om", "orm"},
|
|
{"or", "ori"},
|
|
{"os", "oss"},
|
|
{"pa", "pan"},
|
|
{"pi", "pli"},
|
|
{"pl", "pol"},
|
|
{"ps", "pus"},
|
|
{"pt", "por"},
|
|
{"qu", "que"},
|
|
{"rm", "roh"},
|
|
{"rn", "run"},
|
|
{"ro", "rum"},
|
|
{"ron", "rum"},
|
|
{"ru", "rus"},
|
|
{"rw", "kin"},
|
|
{"sa", "san"},
|
|
{"sc", "srd"},
|
|
{"sd", "snd"},
|
|
{"se", "sme"},
|
|
{"sg", "sag"},
|
|
{"si", "sin"},
|
|
{"sk", "slo"},
|
|
{"sl", "slv"},
|
|
{"slk", "slo"},
|
|
{"sm", "smo"},
|
|
{"sn", "sna"},
|
|
{"so", "som"},
|
|
{"sq", "alb"},
|
|
{"sqi", "alb"},
|
|
{"sr", "srp"},
|
|
{"ss", "ssw"},
|
|
{"st", "sot"},
|
|
{"su", "sun"},
|
|
{"sv", "swe"},
|
|
{"sw", "swa"},
|
|
{"ta", "tam"},
|
|
{"te", "tel"},
|
|
{"tg", "tgk"},
|
|
{"th", "tha"},
|
|
{"ti", "tir"},
|
|
{"tk", "tuk"},
|
|
{"tl", "tgl"},
|
|
{"tn", "tsn"},
|
|
{"to", "ton"},
|
|
{"tr", "tur"},
|
|
{"ts", "tso"},
|
|
{"tt", "tat"},
|
|
{"tw", "twi"},
|
|
{"ty", "tah"},
|
|
{"ug", "uig"},
|
|
{"uk", "ukr"},
|
|
{"ur", "urd"},
|
|
{"uz", "uzb"},
|
|
{"ve", "ven"},
|
|
{"vi", "vie"},
|
|
{"vo", "vol"},
|
|
{"wa", "wln"},
|
|
{"wo", "wol"},
|
|
{"xh", "xho"},
|
|
{"yi", "yid"},
|
|
{"yo", "yor"},
|
|
{"za", "zha"},
|
|
{"zh", "chi"},
|
|
{"zho", "chi"},
|
|
{"zu", "zul"},
|
|
};
|
|
|
|
struct langsearch {
|
|
const char *str;
|
|
size_t size;
|
|
};
|
|
|
|
static int lang_compare(const void *s, const void *k)
|
|
{
|
|
const struct langsearch *search = s;
|
|
const struct lang *key = k;
|
|
|
|
int ret = strncasecmp(search->str, key->match, search->size);
|
|
if (!ret && search->size < sizeof(key->match) && key->match[search->size])
|
|
return 1;
|
|
return ret;
|
|
}
|
|
|
|
static void canonicalize(const char **lang, size_t *size)
|
|
{
|
|
if (*size > sizeof(langmap[0].match))
|
|
return;
|
|
|
|
struct langsearch search = {*lang, *size};
|
|
struct lang *l = bsearch(&search, langmap, MP_ARRAY_SIZE(langmap), sizeof(langmap[0]),
|
|
&lang_compare);
|
|
|
|
if (l) {
|
|
*lang = l->canonical;
|
|
*size = strnlen(l->canonical, sizeof(l->canonical));
|
|
}
|
|
}
|
|
|
|
static bool tag_matches(const char *l1, size_t s1, const char *l2, size_t s2)
|
|
{
|
|
return s1 == s2 && !strncasecmp(l1, l2, s1);
|
|
}
|
|
|
|
int mp_match_lang_single(const char *l1, const char *l2)
|
|
{
|
|
// We never consider null or empty strings to match
|
|
if (!l1 || !l2 || !*l1 || !*l2)
|
|
return 0;
|
|
|
|
// The first subtag should always be a language; canonicalize to 3-letter ISO 639-2B (arbitrarily chosen)
|
|
size_t s1 = strcspn(l1, "-_");
|
|
size_t s2 = strcspn(l2, "-_");
|
|
|
|
const char *l1c = l1;
|
|
const char *l2c = l2;
|
|
size_t s1c = s1;
|
|
size_t s2c = s2;
|
|
|
|
canonicalize(&l1c, &s1c);
|
|
canonicalize(&l2c, &s2c);
|
|
|
|
// If the first subtags don't match, we have no match at all
|
|
if (!tag_matches(l1c, s1c, l2c, s2c))
|
|
return 0;
|
|
|
|
// Attempt to match each subtag in each string against each in the other
|
|
int score = 1;
|
|
bool x1 = false;
|
|
int count = 0;
|
|
for (;;) {
|
|
l1 += s1;
|
|
|
|
while (*l1 == '-' || *l1 == '_')
|
|
l1++;
|
|
|
|
if (!*l1)
|
|
break;
|
|
|
|
s1 = strcspn(l1, "-_");
|
|
if (tag_matches(l1, s1, "x", 1)) {
|
|
x1 = true;
|
|
continue;
|
|
}
|
|
|
|
const char *l2o = l2;
|
|
size_t s2o = s2;
|
|
bool x2 = false;
|
|
for (;;) {
|
|
l2 += s2;
|
|
|
|
while (*l2 == '-' || *l2 == '_')
|
|
l2++;
|
|
|
|
if (!*l2)
|
|
break;
|
|
|
|
s2 = strcspn(l2, "-_");
|
|
if (tag_matches(l2, s2, "x", 1)) {
|
|
x2 = true;
|
|
if (!x1)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
// Private-use subtags only match against other private-use subtags
|
|
if (x1 && !x2)
|
|
continue;
|
|
|
|
if (tag_matches(l1c, s1c, l2c, s2c)) {
|
|
// Matches for subtags earlier in the user's string take priority over later ones,
|
|
// for up to LANGUAGE_SCORE_BITS subtags
|
|
int shift = (LANGUAGE_SCORE_BITS - count - 1);
|
|
if (shift < 0)
|
|
shift = 0;
|
|
score += (1 << shift);
|
|
|
|
if (score >= LANGUAGE_SCORE_MAX)
|
|
return LANGUAGE_SCORE_MAX;
|
|
}
|
|
}
|
|
|
|
l2 = l2o;
|
|
s2 = s2o;
|
|
|
|
count++;
|
|
}
|
|
|
|
return score;
|
|
}
|