diff --git a/maps/scripts.d.ts b/maps/scripts.d.ts new file mode 100644 index 0000000..8bcca68 --- /dev/null +++ b/maps/scripts.d.ts @@ -0,0 +1 @@ +export const mapScripts: Record diff --git a/maps/scripts.js b/maps/scripts.js new file mode 100644 index 0000000..5a85516 --- /dev/null +++ b/maps/scripts.js @@ -0,0 +1,2 @@ +// Generated by scripts/build-maps.mjs from interscript/maps — source script per system. Do not edit. +export const mapScripts = {"acadsin-zho-Hani-Latn-2002":"Hani","alalc-amh-Ethi-Latn-1997":"Ethi","alalc-amh-Ethi-Latn-2011":"Ethi","alalc-ara-Arab-Latn-1997":"Arab","alalc-asm-Deva-Latn-1997":"Deva","alalc-asm-Deva-Latn-2012":"Deva","alalc-aze-Arab-Latn-1997":"Arab","alalc-aze-Cyrl-Latn-1997":"Cyrl","alalc-bel-Cyrl-Latn-1997":"Cyrl","alalc-ben-Beng-Latn-1997":"Beng","alalc-ben-Beng-Latn-2017":"Beng","alalc-bul-Cyrl-Latn-1997":"Cyrl","alalc-div-Thaa-Latn-1997":"Thaa","alalc-div-Thaa-Latn-2012":"Thaa","alalc-ell-Grek-Latn-1997":"Grek","alalc-ell-Grek-Latn-2010":"Grek","alalc-fas-Arab-Latn-1997":"Arab","alalc-guj-Gujr-Latn-1997":"Gujr","alalc-guj-Gujr-Latn-2011":"Gujr","alalc-hin-Deva-Latn-1997":"Deva","alalc-hin-Deva-Latn-2011":"Deva","alalc-kan-Kana-Latn-1997":"Kana","alalc-kan-Kana-Latn-2011":"Kana","alalc-kat-Geok-Latn-1997":"Geok","alalc-kat-Geok-Latn-2011":"Geok","alalc-kat-Geor-Latn-1997":"Geor","alalc-kat-Geor-Latn-2011":"Geor","alalc-kor-Hang-Latn-1997":"Hang","alalc-mal-Mlym-Latn-1997":"Mlym","alalc-mal-Mlym-Latn-2012":"Mlym","alalc-mar-Deva-Latn-1997":"Deva","alalc-mar-Deva-Latn-2011":"Deva","alalc-mkd-Cyrl-Latn-1997":"Cyrl","alalc-mkd-Cyrl-Latn-2013":"Cyrl","alalc-mon-Cyrl-Latn-1997":"Cyrl","alalc-ori-Orya-Latn-1997":"Orya","alalc-ori-Orya-Latn-2011":"Orya","alalc-pan-Guru-Latn-1997":"Guru","alalc-pan-Guru-Latn-2011":"Guru","alalc-pli-Deva-Latn-2012":"Deva","alalc-pra-Deva-Latn-2012":"Deva","alalc-rus-Cyrl-Latn-1997":"Cyrl","alalc-rus-Cyrl-Latn-2012":"Cyrl","alalc-san-Deva-Latn-2012":"Deva","alalc-sin-Sinh-Latn-1997":"Sinh","alalc-sin-Sinh-Latn-2011":"Sinh","alalc-srp-Cyrl-Latn-1997":"Cyrl","alalc-srp-Cyrl-Latn-2013":"Cyrl","alalc-tam-Taml-Latn-1997":"Taml","alalc-tam-Taml-Latn-2011":"Taml","alalc-tel-Telu-Latn-1997":"Telu","alalc-tel-Telu-Latn-2011":"Telu","alalc-tir-Ethi-Latn-1997":"Ethi","alalc-tir-Ethi-Latn-2011":"Ethi","alalc-ukr-Cyrl-Latn-1997":"Cyrl","alalc-ukr-Cyrl-Latn-2011":"Cyrl","apcbg-bul-Cyrl-Latn-1995":"Cyrl","az-aze-Cyrl-Latn-1939":"Cyrl","az-aze-Cyrl-Latn-1958":"Cyrl","bas-rus-Cyrl-Latn-2017-bss":"Cyrl","bas-rus-Cyrl-Latn-2017-oss":"Cyrl","bgn-jpn-Hrkt-Latn-1962":"Hrkt","bgn-kor-Hang-Latn-1943":"Hang","bgn-kor-Kore-Latn-1943":"Kore","bgna-bul-Cyrl-Latn-2006":"Cyrl","bgna-bul-Cyrl-Latn-2009":"Cyrl","bgnpcgn-amh-Ethi-Latn-1967":"Ethi","bgnpcgn-ara-Arab-Latn-1956":"Arab","bgnpcgn-arm-Armn-Latn-1981":"Armn","bgnpcgn-aze-Cyrl-Latn-1993":"Cyrl","bgnpcgn-bak-Cyrl-Latn-2007":"Cyrl","bgnpcgn-bal-Arab-Latn-2008":"Arab","bgnpcgn-bel-Cyrl-Latn-1979":"Cyrl","bgnpcgn-bul-Cyrl-Latn-1952":"Cyrl","bgnpcgn-bul-Cyrl-Latn-2013":"Cyrl","bgnpcgn-che-Cyrl-Latn-2008":"Cyrl","bgnpcgn-deu-Latn-Latn-2000":"Latn","bgnpcgn-div-Thaa-Latn-1972":"Thaa","bgnpcgn-div-Thaa-Latn-1988":"Thaa","bgnpcgn-ell-Grek-Latn-1962":"Grek","bgnpcgn-ell-Grek-Latn-1996":"Grek","bgnpcgn-fao-Latn-Latn-1964":"Latn","bgnpcgn-fao-Latn-Latn-1968":"Latn","bgnpcgn-fas-Arab-Latn-1956":"Arab","bgnpcgn-isl-Latn-Latn-1964":"Latn","bgnpcgn-isl-Latn-Latn-1968":"Latn","bgnpcgn-jpn-Hrkt-Latn-1976":"Hrkt","bgnpcgn-kat-Geor-Latn-1981":"Geor","bgnpcgn-kat-Geor-Latn-2009":"Geor","bgnpcgn-kaz-Cyrl-Latn-1979":"Cyrl","bgnpcgn-kir-Cyrl-Latn-1979":"Cyrl","bgnpcgn-kor-Hang-Latn-kn-1945":"Hang","bgnpcgn-kor-Hang-Latn-rok-2011":"Hang","bgnpcgn-kor-Kore-Latn-rok-2011":"Kore","bgnpcgn-kur-Arab-Latn-2007":"Arab","bgnpcgn-mkd-Cyrl-Latn-1981":"Cyrl","bgnpcgn-mkd-Cyrl-Latn-2013":"Cyrl","bgnpcgn-mon-Cyrl-Latn-1964":"Cyrl","bgnpcgn-nep-Deva-Latn-2011":"Deva","bgnpcgn-per-Arab-Latn-1958":"Arab","bgnpcgn-prs-Arab-Latn-2007":"Arab","bgnpcgn-prs-Arab-Latn-yaghoubi":"Arab","bgnpcgn-pus-Arab-Latn-1968":"Arab","bgnpcgn-ron-Cyrl-Latn-2002":"Cyrl","bgnpcgn-rue-Cyrl-Latn-2016":"Cyrl","bgnpcgn-rus-Cyrl-Latn-1947":"Cyrl","bgnpcgn-sme-Latn-Latn-1984":"Latn","bgnpcgn-srp-Cyrl-Latn-1962":"Cyrl","bgnpcgn-srp-Cyrl-Latn-2005":"Cyrl","bgnpcgn-tat-Cyrl-Latn-2007":"Cyrl","bgnpcgn-tgk-Cyrl-Latn-1994":"Cyrl","bgnpcgn-tir-Ethi-Latn-2007":"Ethi","bgnpcgn-tuk-Cyrl-Latn-1979":"Cyrl","bgnpcgn-tuk-Cyrl-Latn-1993":"Cyrl","bgnpcgn-ukr-Cyrl-Latn-1965":"Cyrl","bgnpcgn-ukr-Cyrl-Latn-2019":"Cyrl","bgnpcgn-urd-Arab-Latn-2007":"Arab","bgnpcgn-uzb-Cyrl-Latn-1979":"Cyrl","bgnpcgn-uzb-Cyrl-Latn-2000":"Cyrl","bgnpcgn-zho-Hans-Latn-1979":"Hans","bis-asm-Beng-Latn-13194-1991":"Beng","bis-ben-Beng-Latn-13194-1991":"Beng","bis-dev-Deva-Latn-13194-1991":"Deva","bis-guj-Gujr-Latn-13194-1991":"Gujr","bis-kan-Kana-Latn-13194-1991":"Kana","bis-mlm-Mlym-Latn-13194-1991":"Mlym","bis-ori-Orya-Latn-13194-1991":"Orya","bis-pnj-Guru-Latn-13194-1991":"Guru","bis-tel-Telu-Latn-13194-1991":"Telu","bis-tml-Taml-Latn-13194-1991":"Taml","by-bel-Cyrl-Latn-1998":"Cyrl","by-bel-Cyrl-Latn-2007":"Cyrl","din-grc-Grek-Latn-31634-2011-t1":"Grek","din-hin-Deva-Latn-33904-2018":"Deva","din-kat-Geor-Latn-32707-2010":"Geor","din-mar-Deva-Latn-33904-2018":"Deva","din-nep-Deva-Latn-33904-2018":"Deva","din-pli-Deva-Latn-33904-2018":"Deva","din-pra-Deva-Latn-33904-2018":"Deva","din-san-Deva-Latn-33904-2018":"Deva","din-tam-Taml-Latn-33903-2016":"Taml","dos-nep-Deva-Latn-1997":"Deva","elot-ell-Grek-Latn-743-1982-tl":"Grek","elot-ell-Grek-Latn-743-1982-ts":"Grek","elot-ell-Grek-Latn-743-2001-tl":"Grek","elot-ell-Grek-Latn-743-2001-ts":"Grek","ggg-kat-Geor-Latn-2002":"Geor","gki-bel-Cyrl-Latn-1992":"Cyrl","gki-bel-Cyrl-Latn-2000":"Cyrl","gost-rus-Cyrl-Latn-16876-71-1983":"Cyrl","gost-rus-Cyrl-Latn-7.79-2000-2002":"Cyrl","hk-yue-Hani-Latn-1888":"Hani","icao-bel-Cyrl-Latn-9303":"Cyrl","icao-bul-Cyrl-Latn-9303":"Cyrl","icao-fas-Arab-Latn-9303":"Arab","icao-heb-Hebr-Latn-9303":"Hebr","icao-mkd-Cyrl-Latn-9303":"Cyrl","icao-rus-Cyrl-Latn-9303":"Cyrl","icao-srp-Cyrl-Latn-9303":"Cyrl","icao-ukr-Cyrl-Latn-9303":"Cyrl","iso-ara-Arab-Latn-233-1984":"Arab","iso-ara-Arab-Latn-233-2-1993":"Arab","iso-asm-Beng-Latn-15919-2001":"Beng","iso-ben-Beng-Latn-15919-2001":"Beng","iso-ell-Grek-Latn-843-1997-t1":"Grek","iso-ell-Grek-Latn-843-1997-t2":"Grek","iso-guj-Gujr-Latn-15919-2001":"Gujr","iso-hin-Deva-Latn-15919-2001":"Deva","iso-inc-Deva-Latn-15919-2001":"Deva","iso-jpn-Hrkt-Latn-3602-1989":"Hrkt","iso-kan-Kana-Latn-15919-2001":"Kana","iso-kat-Geor-Latn-9984-1996":"Geor","iso-kor-Hang-Latn-1996-method1":"Hang","iso-kor-Hang-Latn-1996-method2":"Hang","iso-mal-Mlym-Latn-15919-2001":"Mlym","iso-mar-Deva-Latn-15919-2001":"Deva","iso-nep-Deva-Latn-15919-2001":"Deva","iso-ori-Orya-Latn-15919-2001":"Orya","iso-pan-Guru-Latn-15919-2001":"Guru","iso-pli-Beng-Latn-15919-2001":"Beng","iso-pli-Deva-Latn-15919-2001":"Deva","iso-pli-Sinh-Latn-15919-2001":"Sinh","iso-pli-Thai-Latn-15919-2001":"Thai","iso-pra-Deva-Latn-15919-2001":"Deva","iso-prs-Arab-Latn-233-3-1999":"Arab","iso-rus-Cyrl-Latn-9-1995":"Cyrl","iso-san-Deva-Latn-15919-2001":"Deva","iso-tam-Taml-Latn-15919-2001":"Taml","iso-tel-Telu-Latn-15919-2001":"Telu","iso-tha-Thai-Latn-11940-1998":"Thai","kp-kor-Hang-Latn-2002":"Hang","lshk-yue-Hani-Latn-jyutping-1993":"Hani","masm-mon-Cyrl-Latn-5217-2012":"Cyrl","masm-mon-Latn-Cyrl-5217-2012":"Latn","mext-jpn-Hrkt-Latn-1954":"Hrkt","moct-kor-Hang-Latn-2000":"Hang","mofa-jpn-Hrkt-Latn-1989":"Hrkt","mv-div-Thaa-Latn-1987":"Thaa","mvd-bel-Cyrl-Latn-2008":"Cyrl","mvd-bel-Cyrl-Latn-2010":"Cyrl","mvd-rus-Cyrl-Latn-2008":"Cyrl","mvd-rus-Cyrl-Latn-2010":"Cyrl","odni-ara-Arab-Latn-2004":"Arab","odni-ara-Arab-Latn-2015":"Arab","odni-aze-Cyrl-Latn-2015":"Cyrl","odni-bel-Cyrl-Latn-2015":"Cyrl","odni-bul-Cyrl-Latn-2005":"Cyrl","odni-bul-Cyrl-Latn-2015":"Cyrl","odni-che-Cyrl-Latn-2015":"Cyrl","odni-fas-Arab-Latn-2004":"Arab","odni-fas-Arab-Latn-2015":"Arab","odni-hin-Deva-Latn-2004":"Deva","odni-hin-Deva-Latn-2015":"Deva","odni-kat-Geor-Latn-2015":"Geor","odni-kaz-Cyrl-Latn-2015":"Cyrl","odni-kir-Cyrl-Latn-2015":"Cyrl","odni-kor-Hang-Latn-2015":"Hang","odni-mkd-Cyrl-Latn-2005":"Cyrl","odni-mkd-Cyrl-Latn-2015":"Cyrl","odni-prs-Arab-Latn-2004":"Arab","odni-prs-Arab-Latn-2015":"Arab","odni-pus-Arab-Latn-2011":"Arab","odni-rus-Cyrl-Latn-2015":"Cyrl","odni-srp-Cyrl-Latn-2005":"Cyrl","odni-srp-Cyrl-Latn-2015":"Cyrl","odni-tat-Cyrl-Latn-2015":"Cyrl","odni-tgk-Cyrl-Latn-2015":"Cyrl","odni-tuk-Cyrl-Latn-2015":"Cyrl","odni-uig-Cyrl-Latn-2015":"Cyrl","odni-ukr-Cyrl-Latn-2015":"Cyrl","odni-urd-Arab-Latn-2015":"Arab","odni-uzb-Cyrl-Latn-2015":"Cyrl","sac-zho-Hans-Latn-1979":"Hans","sasm-mon-Mong-Latn-general-1978":"Mong","sasm-mon-Mong-Latn-phonetic-1978":"Mong","ses-ara-Arab-Latn-1930":"Arab","stategeocadastre-ukr-Cyrl-Latn-1993":"Cyrl","ua-ukr-Cyrl-Latn-1996":"Cyrl","ua-ukr-Cyrl-Latn-2007":"Cyrl","ua-ukr-Cyrl-Latn-2010":"Cyrl","un-amh-Ethi-Latn-2016":"Ethi","un-ara-Arab-Latn-1971":"Arab","un-ara-Arab-Latn-1972":"Arab","un-ara-Arab-Latn-2017":"Arab","un-asm-Beng-Latn-1972":"Beng","un-bel-Cyrl-Latn-2007":"Cyrl","un-ben-Beng-Latn-2016":"Beng","un-bul-Cyrl-Latn-1977":"Cyrl","un-ell-Grek-Latn-1987-phonetic":"Grek","un-ell-Grek-Latn-1987-tl":"Grek","un-ell-Grek-Latn-1987-ts":"Grek","un-guj-Gujr-Latn-1972":"Gujr","un-hin-Deva-Latn-2016":"Deva","un-kan-Kana-Latn-2016":"Kana","un-mal-Mlym-Latn-1972":"Mlym","un-mar-Deva-Latn-2016":"Deva","un-mkd-Cyrl-Latn-1977":"Cyrl","un-mon-Mong-Latn-general-2013":"Mong","un-mon-Mong-Latn-phonetic-2013":"Mong","un-nep-Deva-Latn-1972":"Deva","un-nep-Deva-Latn-2013":"Deva","un-ori-Orya-Latn-1972":"Orya","un-pan-Guru-Latn-1972":"Guru","un-prs-Arab-Latn-1967":"Arab","un-rus-Cyrl-Latn-1987":"Cyrl","un-sin-Sinh-Latn-1972":"Sinh","un-srp-Cyrl-Latn-1997":"Cyrl","un-tam-Taml-Latn-1972":"Taml","un-tel-Telu-Latn-1972":"Telu","un-ukr-Cyrl-Latn-1998":"Cyrl","un-ukr-Cyrl-Latn-2012":"Cyrl","un-urd-Arab-Latn-1972":"Arab","var-amh-Ethi-Latn-eae-2003":"Ethi","var-ara-Arab-Arab-rababa":"Arab","var-gez-Ethi-Latn-eae-2003":"Ethi","var-hin-Deva-Latn-hunterian-1872":"Deva","var-jpn-Hrkt-Latn-hepburn-1886":"Hrkt","var-jpn-Hrkt-Latn-hepburn-1954":"Hrkt","var-kor-Hang-Hang-jamo":"Hang","var-kor-Hang-Latn-mr-1939":"Hang","var-kor-Kore-Hang-2013":"Kore","var-kor-Kore-Latn-mr-1939":"Kore","var-mar-Deva-Latn-hunterian-1872":"Deva","var-mon-Mong-Latn-1930":"Mong","var-mon-Mong-Latn-lessing":"Mong","var-mon-Mong-Latn-vpmc":"Mong","var-pra-Deva-Latn-iast-1912":"Deva","var-san-Deva-Latn-iast-1912":"Deva","var-zho-Hani-Latn-wd-1979":"Hani"}; diff --git a/scripts/build-maps.mjs b/scripts/build-maps.mjs index fe38946..5c8ad31 100644 --- a/scripts/build-maps.mjs +++ b/scripts/build-maps.mjs @@ -50,5 +50,22 @@ writeFileSync( ) writeFileSync(join(repoMaps, "manifest.d.ts"), "export const systemCodes: readonly string[]\n") +const mapScripts = {} +for (const name of readdirSync(outMaps)) { + const code = basename(name, ".json") + if (manifest.systems.includes(code)) { + mapScripts[code] = JSON.parse(readFileSync(join(outMaps, name), "utf8")).metadata?.source_script + } +} +writeFileSync( + join(repoMaps, "scripts.js"), + "// Generated by scripts/build-maps.mjs from interscript/maps — source script per system. Do not edit.\n" + + `export const mapScripts = ${JSON.stringify(mapScripts)};\n`, +) +writeFileSync( + join(repoMaps, "scripts.d.ts"), + "export const mapScripts: Record\n", +) + rmSync(outDir, { recursive: true, force: true }) console.log(`bundled ${manifest.count} maps into maps/`) diff --git a/src/engine.ts b/src/engine.ts index 344c075..282b45f 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -4,6 +4,8 @@ */ import { configure, transliterateAsync, type SystemCode } from "interscript" import { bundledSystemCodes, detectableSystemCodes, loadMap, type MapAssets } from "./maps.js" +import { mapScripts } from "../maps/scripts.js" +import { detectInputScript, sourceMatchesScript } from "./scripts.js" import type { LoadStrategy } from "interscript" let configuredFor: MapAssets | undefined @@ -43,18 +45,26 @@ function levenshtein(a: string, b: string): number { return prev[b.length]! } -// Mirrors interscript-ruby's detector: transliterate the input through -// every system, rank by Levenshtein distance to the output. Systems -// that fail (including ML-powered maps with no runtime configured) are -// skipped, exactly as the Ruby detector rescues per-map errors. +// Transliterate the input through every system whose source script +// matches the input, rank by Levenshtein distance to the output. +// Systems that fail (including ML-powered maps with no runtime +// configured) are skipped, exactly as the Ruby detector rescues +// per-map errors. The full-corpus walk is gone deliberately: it +// exceeded the Workers CPU budget (1102s in production). export async function detect( assets: MapAssets, input: string, output: string, ): Promise<{ mapName: string; distance: number }[]> { ensureEngine(assets) + // Only systems whose source script matches the input can rank: walking + // all 288 maps exhausts the Workers CPU budget (1102s in production). + const detected = detectInputScript(input) + const codes = detected + ? detectableSystemCodes().filter((code) => sourceMatchesScript(mapScripts[code], detected)) + : [] const candidates: { mapName: string; distance: number }[] = [] - for (const code of detectableSystemCodes()) { + for (const code of codes) { try { const transliterated = await transliterateAsync(code, input) candidates.push({ mapName: code, distance: levenshtein(transliterated, output) }) diff --git a/src/rest.ts b/src/rest.ts index 90993fe..365f9b2 100644 --- a/src/rest.ts +++ b/src/rest.ts @@ -7,7 +7,8 @@ * GET /v1/maps every bundled transliteration system code * GET /v1/maps/:code one system's compiled map (JSON IR) * POST /v1/transliterate {system, input} -> {output} - * POST /v1/detect {input, output} -> ranked systems + * POST /v1/detect {input, output} -> systems whose source + * script matches the input, ranked * GET /v1/models the neural model index (IMF v1) * GET /v1/models/:id one model's full entry * POST /v1/infer {model, input} -> {output} (Modal proxy) diff --git a/src/scripts.ts b/src/scripts.ts new file mode 100644 index 0000000..0b17070 --- /dev/null +++ b/src/scripts.ts @@ -0,0 +1,152 @@ +/** + * Input-script detection for detect(): the walk only transliterates + * systems whose source script matches the input — loading all 288 maps + * per request exceeds the Workers CPU budget (1102s in production). + * Ranges cover exactly the source scripts present in the corpus. + */ + +type ScriptName = string + +const SCRIPT_RANGES: ReadonlyArray< + readonly [ScriptName, ReadonlyArray] +> = [ + [ + "Latn", + [ + [0x41, 0x5a], + [0x61, 0x7a], + [0xc0, 0xff], + [0x100, 0x24f], + ], + ], + [ + "Grek", + [ + [0x370, 0x3ff], + [0x1f00, 0x1fff], + ], + ], + ["Cyrl", [[0x400, 0x52f]]], + ["Armn", [[0x530, 0x58f]]], + ["Hebr", [[0x590, 0x5ff]]], + [ + "Arab", + [ + [0x600, 0x6ff], + [0x750, 0x77f], + [0xfb50, 0xfdff], + [0xfe70, 0xfeff], + ], + ], + ["Deva", [[0x900, 0x97f]]], + ["Beng", [[0x980, 0x9ff]]], + ["Guru", [[0xa00, 0xa7f]]], + ["Gujr", [[0xa80, 0xaff]]], + ["Orya", [[0xb00, 0xb7f]]], + ["Taml", [[0xb80, 0xbff]]], + ["Telu", [[0xc00, 0xc7f]]], + ["Kana", [[0xc80, 0xcff]]], + ["Mlym", [[0xd00, 0xd7f]]], + ["Sinh", [[0xd80, 0xdff]]], + ["Thaa", [[0x780, 0x7bf]]], + ["Thai", [[0xe00, 0xe7f]]], + [ + "Geor", + [ + [0x10a0, 0x10ff], + [0x1c90, 0x1cbf], + ], + ], + ["Ethi", [[0x1200, 0x137f]]], + ["Mong", [[0x1800, 0x18af]]], + [ + "Hang", + [ + [0x1100, 0x11ff], + [0x3130, 0x318f], + [0xac00, 0xd7af], + ], + ], + [ + "Hrkt", + [ + [0x3040, 0x309f], + [0x30a0, 0x30ff], + [0x31f0, 0x31ff], + ], + ], + [ + "Hani", + [ + [0x2e80, 0x2eff], + [0x3400, 0x4dbf], + [0x4e00, 0x9fff], + [0xf900, 0xfaff], + [0x20000, 0x2a6df], + ], + ], +] + +function scriptOfCodePoint(cp: number): ScriptName | null { + for (const [script, ranges] of SCRIPT_RANGES) { + for (const [lo, hi] of ranges) { + if (cp >= lo && cp <= hi) return script + } + } + return null +} + +export function detectInputScript(input: string): ScriptName | null { + const counts = new Map() + for (const ch of input) { + const script = scriptOfCodePoint(ch.codePointAt(0)!) + if (script) counts.set(script, (counts.get(script) ?? 0) + 1) + } + let best: ScriptName | null = null + let bestWeighted = 0 + for (const [script, n] of counts) { + // A Latin majority is the weakest signal: one stray non-Latin letter + // outranks any amount of Latin. + const weighted = script === "Latn" ? n - 0.5 : n + if (weighted > bestWeighted) { + best = script + bestWeighted = weighted + } + } + return best +} + +// Detected script → corpus source_script values that can legitimately +// process it ("Kana" is Kannada in this corpus; "Kore" is the mixed +// Hangul+Latin jamo family; "Hrkt" is Japanese kana). +const FAMILIES: Readonly> = { + Latn: ["Latn"], + Grek: ["Grek"], + Cyrl: ["Cyrl"], + Armn: ["Armn"], + Hebr: ["Hebr"], + Arab: ["Arab"], + Deva: ["Deva"], + Beng: ["Beng"], + Guru: ["Guru"], + Gujr: ["Gujr"], + Orya: ["Orya"], + Taml: ["Taml"], + Telu: ["Telu"], + Kana: ["Kana"], + Mlym: ["Mlym"], + Sinh: ["Sinh"], + Thai: ["Thai"], + Thaa: ["Thaa"], + Ethi: ["Ethi"], + Mong: ["Mong"], + Geor: ["Geor"], + Hang: ["Hang", "Kore"], + Hrkt: ["Hrkt", "Kore"], + Hani: ["Hani", "Hans", "Kore", "Hrkt"], +} + +export function sourceMatchesScript(sourceScript: string | undefined, detected: string): boolean { + const family = FAMILIES[detected] + return !!sourceScript && !!family && family.includes(sourceScript) +} diff --git a/test/detect.test.ts b/test/detect.test.ts new file mode 100644 index 0000000..2cd7a59 --- /dev/null +++ b/test/detect.test.ts @@ -0,0 +1,71 @@ +import { readFileSync } from "node:fs" +import { describe, expect, it } from "vitest" +import { detect } from "../src/engine.js" +import { detectInputScript } from "../src/scripts.js" +import type { MapAssets } from "../src/maps.js" + +const fetched: string[] = [] +const env: MapAssets = { + fetch: async (url) => { + const path = typeof url === "string" ? url : url.url + const code = path.substring(path.lastIndexOf("/") + 1).replace(".json", "") + fetched.push(code) + try { + return new Response(readFileSync(`maps/${code}.json`), { status: 200 }) + } catch { + return new Response(null, { status: 404 }) + } + }, +} + +describe("detectInputScript", () => { + it("detects the majority script of the input", () => { + expect(detectInputScript("ქართული")).toBe("Geor") + expect(detectInputScript("한국")).toBe("Hang") + expect(detectInputScript("Привет")).toBe("Cyrl") + expect(detectInputScript(" தமிழ் ")).toBe("Taml") + }) + + it("prefers the non-Latin script in mixed input", () => { + expect(detectInputScript("한국a")).toBe("Hang") + expect(detectInputScript("київ word")).toBe("Cyrl") + }) + + it("falls back to Latin for pure ASCII", () => { + expect(detectInputScript("hello world")).toBe("Latn") + }) + + it("returns null when no script characters are present", () => { + expect(detectInputScript("123 !?")).toBeNull() + expect(detectInputScript("")).toBeNull() + }) +}) + +describe("detect prefilter", () => { + it("only loads maps whose source script matches the input", async () => { + fetched.length = 0 + const results = await detect(env, "ქართული", "kʻartʻuli") + // 8 Georgian-source systems, not the full 288-map walk. + expect(fetched.length).toBeLessThanOrEqual(10) + expect(results[0]).toMatchObject({ mapName: "alalc-kat-Geor-Latn-1997", distance: 0 }) + }) + + it("matches Korean-family sources for Hangul input", async () => { + fetched.length = 0 + const results = await detect(env, "한국", "Hanguk") + expect(results[0]!.distance).toBe(0) + expect(results.map((r) => r.mapName)).toContain("var-kor-Kore-Latn-mr-1939") + }) + + it("ranks Han-source systems for Han input", async () => { + const results = await detect(env, "阜康", "Fukang") + expect(results[0]).toMatchObject({ mapName: "acadsin-zho-Hani-Latn-2002" }) + }) + + it("returns an empty ranking for script-less input", async () => { + fetched.length = 0 + const results = await detect(env, "123 !?", "x") + expect(results).toEqual([]) + expect(fetched).toEqual([]) + }) +}) diff --git a/test/fixtures/parity.json b/test/fixtures/parity.json index bea6323..7188987 100644 --- a/test/fixtures/parity.json +++ b/test/fixtures/parity.json @@ -137,1126 +137,6 @@ { "mapName": "odni-kat-Geor-Latn-2015", "distance": 2 - }, - { - "mapName": "acadsin-zho-Hani-Latn-2002", - "distance": 9 - }, - { - "mapName": "alalc-amh-Ethi-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-amh-Ethi-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-ara-Arab-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-asm-Deva-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-asm-Deva-Latn-2012", - "distance": 9 - }, - { - "mapName": "alalc-aze-Arab-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-aze-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-bel-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-ben-Beng-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-ben-Beng-Latn-2017", - "distance": 9 - }, - { - "mapName": "alalc-bul-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-div-Thaa-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-div-Thaa-Latn-2012", - "distance": 9 - }, - { - "mapName": "alalc-ell-Grek-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-ell-Grek-Latn-2010", - "distance": 9 - }, - { - "mapName": "alalc-fas-Arab-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-guj-Gujr-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-guj-Gujr-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-hin-Deva-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-hin-Deva-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-kan-Kana-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-kan-Kana-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-kat-Geok-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-kat-Geok-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-kor-Hang-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-mal-Mlym-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-mal-Mlym-Latn-2012", - "distance": 9 - }, - { - "mapName": "alalc-mar-Deva-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-mar-Deva-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-mkd-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-mkd-Cyrl-Latn-2013", - "distance": 9 - }, - { - "mapName": "alalc-mon-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-ori-Orya-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-ori-Orya-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-pan-Guru-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-pan-Guru-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-pli-Deva-Latn-2012", - "distance": 9 - }, - { - "mapName": "alalc-pra-Deva-Latn-2012", - "distance": 9 - }, - { - "mapName": "alalc-rus-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-rus-Cyrl-Latn-2012", - "distance": 9 - }, - { - "mapName": "alalc-san-Deva-Latn-2012", - "distance": 9 - }, - { - "mapName": "alalc-sin-Sinh-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-sin-Sinh-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-srp-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-srp-Cyrl-Latn-2013", - "distance": 9 - }, - { - "mapName": "alalc-tam-Taml-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-tam-Taml-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-tel-Telu-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-tel-Telu-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-tir-Ethi-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-tir-Ethi-Latn-2011", - "distance": 9 - }, - { - "mapName": "alalc-ukr-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "alalc-ukr-Cyrl-Latn-2011", - "distance": 9 - }, - { - "mapName": "apcbg-bul-Cyrl-Latn-1995", - "distance": 9 - }, - { - "mapName": "az-aze-Cyrl-Latn-1939", - "distance": 9 - }, - { - "mapName": "az-aze-Cyrl-Latn-1958", - "distance": 9 - }, - { - "mapName": "bas-rus-Cyrl-Latn-2017-bss", - "distance": 9 - }, - { - "mapName": "bas-rus-Cyrl-Latn-2017-oss", - "distance": 9 - }, - { - "mapName": "bgn-jpn-Hrkt-Latn-1962", - "distance": 9 - }, - { - "mapName": "bgn-kor-Hang-Latn-1943", - "distance": 9 - }, - { - "mapName": "bgn-kor-Kore-Latn-1943", - "distance": 9 - }, - { - "mapName": "bgna-bul-Cyrl-Latn-2006", - "distance": 9 - }, - { - "mapName": "bgna-bul-Cyrl-Latn-2009", - "distance": 9 - }, - { - "mapName": "bgnpcgn-amh-Ethi-Latn-1967", - "distance": 9 - }, - { - "mapName": "bgnpcgn-ara-Arab-Latn-1956", - "distance": 9 - }, - { - "mapName": "bgnpcgn-arm-Armn-Latn-1981", - "distance": 9 - }, - { - "mapName": "bgnpcgn-aze-Cyrl-Latn-1993", - "distance": 9 - }, - { - "mapName": "bgnpcgn-bak-Cyrl-Latn-2007", - "distance": 9 - }, - { - "mapName": "bgnpcgn-bal-Arab-Latn-2008", - "distance": 9 - }, - { - "mapName": "bgnpcgn-bel-Cyrl-Latn-1979", - "distance": 9 - }, - { - "mapName": "bgnpcgn-bul-Cyrl-Latn-1952", - "distance": 9 - }, - { - "mapName": "bgnpcgn-bul-Cyrl-Latn-2013", - "distance": 9 - }, - { - "mapName": "bgnpcgn-che-Cyrl-Latn-2008", - "distance": 9 - }, - { - "mapName": "bgnpcgn-deu-Latn-Latn-2000", - "distance": 9 - }, - { - "mapName": "bgnpcgn-div-Thaa-Latn-1972", - "distance": 9 - }, - { - "mapName": "bgnpcgn-div-Thaa-Latn-1988", - "distance": 9 - }, - { - "mapName": "bgnpcgn-ell-Grek-Latn-1962", - "distance": 9 - }, - { - "mapName": "bgnpcgn-ell-Grek-Latn-1996", - "distance": 9 - }, - { - "mapName": "bgnpcgn-fao-Latn-Latn-1964", - "distance": 9 - }, - { - "mapName": "bgnpcgn-fao-Latn-Latn-1968", - "distance": 9 - }, - { - "mapName": "bgnpcgn-fas-Arab-Latn-1956", - "distance": 9 - }, - { - "mapName": "bgnpcgn-isl-Latn-Latn-1964", - "distance": 9 - }, - { - "mapName": "bgnpcgn-isl-Latn-Latn-1968", - "distance": 9 - }, - { - "mapName": "bgnpcgn-jpn-Hrkt-Latn-1976", - "distance": 9 - }, - { - "mapName": "bgnpcgn-kaz-Cyrl-Latn-1979", - "distance": 9 - }, - { - "mapName": "bgnpcgn-kir-Cyrl-Latn-1979", - "distance": 9 - }, - { - "mapName": "bgnpcgn-kor-Hang-Latn-kn-1945", - "distance": 9 - }, - { - "mapName": "bgnpcgn-kor-Hang-Latn-rok-2011", - "distance": 9 - }, - { - "mapName": "bgnpcgn-kor-Kore-Latn-rok-2011", - "distance": 9 - }, - { - "mapName": "bgnpcgn-kur-Arab-Latn-2007", - "distance": 9 - }, - { - "mapName": "bgnpcgn-mkd-Cyrl-Latn-1981", - "distance": 9 - }, - { - "mapName": "bgnpcgn-mkd-Cyrl-Latn-2013", - "distance": 9 - }, - { - "mapName": "bgnpcgn-mon-Cyrl-Latn-1964", - "distance": 9 - }, - { - "mapName": "bgnpcgn-nep-Deva-Latn-2011", - "distance": 9 - }, - { - "mapName": "bgnpcgn-per-Arab-Latn-1958", - "distance": 9 - }, - { - "mapName": "bgnpcgn-prs-Arab-Latn-2007", - "distance": 9 - }, - { - "mapName": "bgnpcgn-prs-Arab-Latn-yaghoubi", - "distance": 9 - }, - { - "mapName": "bgnpcgn-pus-Arab-Latn-1968", - "distance": 9 - }, - { - "mapName": "bgnpcgn-ron-Cyrl-Latn-2002", - "distance": 9 - }, - { - "mapName": "bgnpcgn-rue-Cyrl-Latn-2016", - "distance": 9 - }, - { - "mapName": "bgnpcgn-rus-Cyrl-Latn-1947", - "distance": 9 - }, - { - "mapName": "bgnpcgn-sme-Latn-Latn-1984", - "distance": 9 - }, - { - "mapName": "bgnpcgn-srp-Cyrl-Latn-1962", - "distance": 9 - }, - { - "mapName": "bgnpcgn-srp-Cyrl-Latn-2005", - "distance": 9 - }, - { - "mapName": "bgnpcgn-tat-Cyrl-Latn-2007", - "distance": 9 - }, - { - "mapName": "bgnpcgn-tgk-Cyrl-Latn-1994", - "distance": 9 - }, - { - "mapName": "bgnpcgn-tir-Ethi-Latn-2007", - "distance": 9 - }, - { - "mapName": "bgnpcgn-tuk-Cyrl-Latn-1979", - "distance": 9 - }, - { - "mapName": "bgnpcgn-tuk-Cyrl-Latn-1993", - "distance": 9 - }, - { - "mapName": "bgnpcgn-ukr-Cyrl-Latn-1965", - "distance": 9 - }, - { - "mapName": "bgnpcgn-ukr-Cyrl-Latn-2019", - "distance": 9 - }, - { - "mapName": "bgnpcgn-urd-Arab-Latn-2007", - "distance": 9 - }, - { - "mapName": "bgnpcgn-uzb-Cyrl-Latn-1979", - "distance": 9 - }, - { - "mapName": "bgnpcgn-uzb-Cyrl-Latn-2000", - "distance": 9 - }, - { - "mapName": "bgnpcgn-zho-Hans-Latn-1979", - "distance": 9 - }, - { - "mapName": "bis-asm-Beng-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-ben-Beng-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-dev-Deva-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-guj-Gujr-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-kan-Kana-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-mlm-Mlym-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-ori-Orya-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-pnj-Guru-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-tel-Telu-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "bis-tml-Taml-Latn-13194-1991", - "distance": 9 - }, - { - "mapName": "by-bel-Cyrl-Latn-1998", - "distance": 9 - }, - { - "mapName": "by-bel-Cyrl-Latn-2007", - "distance": 9 - }, - { - "mapName": "din-grc-Grek-Latn-31634-2011-t1", - "distance": 9 - }, - { - "mapName": "din-hin-Deva-Latn-33904-2018", - "distance": 9 - }, - { - "mapName": "din-mar-Deva-Latn-33904-2018", - "distance": 9 - }, - { - "mapName": "din-nep-Deva-Latn-33904-2018", - "distance": 9 - }, - { - "mapName": "din-pli-Deva-Latn-33904-2018", - "distance": 9 - }, - { - "mapName": "din-pra-Deva-Latn-33904-2018", - "distance": 9 - }, - { - "mapName": "din-san-Deva-Latn-33904-2018", - "distance": 9 - }, - { - "mapName": "din-tam-Taml-Latn-33903-2016", - "distance": 9 - }, - { - "mapName": "dos-nep-Deva-Latn-1997", - "distance": 9 - }, - { - "mapName": "elot-ell-Grek-Latn-743-1982-tl", - "distance": 9 - }, - { - "mapName": "elot-ell-Grek-Latn-743-1982-ts", - "distance": 9 - }, - { - "mapName": "elot-ell-Grek-Latn-743-2001-tl", - "distance": 9 - }, - { - "mapName": "elot-ell-Grek-Latn-743-2001-ts", - "distance": 9 - }, - { - "mapName": "gki-bel-Cyrl-Latn-1992", - "distance": 9 - }, - { - "mapName": "gki-bel-Cyrl-Latn-2000", - "distance": 9 - }, - { - "mapName": "gost-rus-Cyrl-Latn-16876-71-1983", - "distance": 9 - }, - { - "mapName": "gost-rus-Cyrl-Latn-7.79-2000-2002", - "distance": 9 - }, - { - "mapName": "icao-bel-Cyrl-Latn-9303", - "distance": 9 - }, - { - "mapName": "icao-bul-Cyrl-Latn-9303", - "distance": 9 - }, - { - "mapName": "icao-fas-Arab-Latn-9303", - "distance": 9 - }, - { - "mapName": "icao-heb-Hebr-Latn-9303", - "distance": 9 - }, - { - "mapName": "icao-mkd-Cyrl-Latn-9303", - "distance": 9 - }, - { - "mapName": "icao-rus-Cyrl-Latn-9303", - "distance": 9 - }, - { - "mapName": "icao-srp-Cyrl-Latn-9303", - "distance": 9 - }, - { - "mapName": "icao-ukr-Cyrl-Latn-9303", - "distance": 9 - }, - { - "mapName": "iso-ara-Arab-Latn-233-1984", - "distance": 9 - }, - { - "mapName": "iso-ara-Arab-Latn-233-2-1993", - "distance": 9 - }, - { - "mapName": "iso-asm-Beng-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-ben-Beng-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-ell-Grek-Latn-843-1997-t1", - "distance": 9 - }, - { - "mapName": "iso-ell-Grek-Latn-843-1997-t2", - "distance": 9 - }, - { - "mapName": "iso-guj-Gujr-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-hin-Deva-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-inc-Deva-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-jpn-Hrkt-Latn-3602-1989", - "distance": 9 - }, - { - "mapName": "iso-kan-Kana-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-kor-Hang-Latn-1996-method1", - "distance": 9 - }, - { - "mapName": "iso-kor-Hang-Latn-1996-method2", - "distance": 9 - }, - { - "mapName": "iso-mal-Mlym-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-mar-Deva-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-nep-Deva-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-ori-Orya-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-pan-Guru-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-pli-Beng-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-pli-Deva-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-pli-Sinh-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-pli-Thai-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-pra-Deva-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-prs-Arab-Latn-233-3-1999", - "distance": 9 - }, - { - "mapName": "iso-rus-Cyrl-Latn-9-1995", - "distance": 9 - }, - { - "mapName": "iso-san-Deva-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-tam-Taml-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-tel-Telu-Latn-15919-2001", - "distance": 9 - }, - { - "mapName": "iso-tha-Thai-Latn-11940-1998", - "distance": 9 - }, - { - "mapName": "kp-kor-Hang-Latn-2002", - "distance": 9 - }, - { - "mapName": "lshk-yue-Hani-Latn-jyutping-1993", - "distance": 9 - }, - { - "mapName": "masm-mon-Cyrl-Latn-5217-2012", - "distance": 9 - }, - { - "mapName": "masm-mon-Latn-Cyrl-5217-2012", - "distance": 9 - }, - { - "mapName": "mext-jpn-Hrkt-Latn-1954", - "distance": 9 - }, - { - "mapName": "moct-kor-Hang-Latn-2000", - "distance": 9 - }, - { - "mapName": "mofa-jpn-Hrkt-Latn-1989", - "distance": 9 - }, - { - "mapName": "mv-div-Thaa-Latn-1987", - "distance": 9 - }, - { - "mapName": "mvd-bel-Cyrl-Latn-2008", - "distance": 9 - }, - { - "mapName": "mvd-bel-Cyrl-Latn-2010", - "distance": 9 - }, - { - "mapName": "mvd-rus-Cyrl-Latn-2008", - "distance": 9 - }, - { - "mapName": "mvd-rus-Cyrl-Latn-2010", - "distance": 9 - }, - { - "mapName": "odni-ara-Arab-Latn-2004", - "distance": 9 - }, - { - "mapName": "odni-ara-Arab-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-aze-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-bel-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-bul-Cyrl-Latn-2005", - "distance": 9 - }, - { - "mapName": "odni-bul-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-che-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-fas-Arab-Latn-2004", - "distance": 9 - }, - { - "mapName": "odni-fas-Arab-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-hin-Deva-Latn-2004", - "distance": 9 - }, - { - "mapName": "odni-hin-Deva-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-kaz-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-kir-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-kor-Hang-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-mkd-Cyrl-Latn-2005", - "distance": 9 - }, - { - "mapName": "odni-mkd-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-prs-Arab-Latn-2004", - "distance": 9 - }, - { - "mapName": "odni-prs-Arab-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-pus-Arab-Latn-2011", - "distance": 9 - }, - { - "mapName": "odni-rus-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-srp-Cyrl-Latn-2005", - "distance": 9 - }, - { - "mapName": "odni-srp-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-tat-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-tgk-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-tuk-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-uig-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-ukr-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-urd-Arab-Latn-2015", - "distance": 9 - }, - { - "mapName": "odni-uzb-Cyrl-Latn-2015", - "distance": 9 - }, - { - "mapName": "sac-zho-Hans-Latn-1979", - "distance": 9 - }, - { - "mapName": "sasm-mon-Mong-Latn-general-1978", - "distance": 9 - }, - { - "mapName": "sasm-mon-Mong-Latn-phonetic-1978", - "distance": 9 - }, - { - "mapName": "ses-ara-Arab-Latn-1930", - "distance": 9 - }, - { - "mapName": "stategeocadastre-ukr-Cyrl-Latn-1993", - "distance": 9 - }, - { - "mapName": "ua-ukr-Cyrl-Latn-1996", - "distance": 9 - }, - { - "mapName": "ua-ukr-Cyrl-Latn-2007", - "distance": 9 - }, - { - "mapName": "ua-ukr-Cyrl-Latn-2010", - "distance": 9 - }, - { - "mapName": "un-amh-Ethi-Latn-2016", - "distance": 9 - }, - { - "mapName": "un-ara-Arab-Latn-1971", - "distance": 9 - }, - { - "mapName": "un-ara-Arab-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-ara-Arab-Latn-2017", - "distance": 9 - }, - { - "mapName": "un-asm-Beng-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-bel-Cyrl-Latn-2007", - "distance": 9 - }, - { - "mapName": "un-ben-Beng-Latn-2016", - "distance": 9 - }, - { - "mapName": "un-bul-Cyrl-Latn-1977", - "distance": 9 - }, - { - "mapName": "un-ell-Grek-Latn-1987-phonetic", - "distance": 9 - }, - { - "mapName": "un-ell-Grek-Latn-1987-tl", - "distance": 9 - }, - { - "mapName": "un-ell-Grek-Latn-1987-ts", - "distance": 9 - }, - { - "mapName": "un-guj-Gujr-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-hin-Deva-Latn-2016", - "distance": 9 - }, - { - "mapName": "un-kan-Kana-Latn-2016", - "distance": 9 - }, - { - "mapName": "un-mal-Mlym-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-mar-Deva-Latn-2016", - "distance": 9 - }, - { - "mapName": "un-mkd-Cyrl-Latn-1977", - "distance": 9 - }, - { - "mapName": "un-mon-Mong-Latn-general-2013", - "distance": 9 - }, - { - "mapName": "un-mon-Mong-Latn-phonetic-2013", - "distance": 9 - }, - { - "mapName": "un-nep-Deva-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-nep-Deva-Latn-2013", - "distance": 9 - }, - { - "mapName": "un-ori-Orya-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-pan-Guru-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-prs-Arab-Latn-1967", - "distance": 9 - }, - { - "mapName": "un-rus-Cyrl-Latn-1987", - "distance": 9 - }, - { - "mapName": "un-sin-Sinh-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-srp-Cyrl-Latn-1997", - "distance": 9 - }, - { - "mapName": "un-tam-Taml-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-tel-Telu-Latn-1972", - "distance": 9 - }, - { - "mapName": "un-ukr-Cyrl-Latn-1998", - "distance": 9 - }, - { - "mapName": "un-ukr-Cyrl-Latn-2012", - "distance": 9 - }, - { - "mapName": "un-urd-Arab-Latn-1972", - "distance": 9 - }, - { - "mapName": "var-amh-Ethi-Latn-eae-2003", - "distance": 9 - }, - { - "mapName": "var-gez-Ethi-Latn-eae-2003", - "distance": 9 - }, - { - "mapName": "var-hin-Deva-Latn-hunterian-1872", - "distance": 9 - }, - { - "mapName": "var-jpn-Hrkt-Latn-hepburn-1886", - "distance": 9 - }, - { - "mapName": "var-jpn-Hrkt-Latn-hepburn-1954", - "distance": 9 - }, - { - "mapName": "var-kor-Hang-Hang-jamo", - "distance": 9 - }, - { - "mapName": "var-kor-Hang-Latn-mr-1939", - "distance": 9 - }, - { - "mapName": "var-kor-Kore-Hang-2013", - "distance": 9 - }, - { - "mapName": "var-kor-Kore-Latn-mr-1939", - "distance": 9 - }, - { - "mapName": "var-mar-Deva-Latn-hunterian-1872", - "distance": 9 - }, - { - "mapName": "var-mon-Mong-Latn-1930", - "distance": 9 - }, - { - "mapName": "var-mon-Mong-Latn-lessing", - "distance": 9 - }, - { - "mapName": "var-mon-Mong-Latn-vpmc", - "distance": 9 - }, - { - "mapName": "var-pra-Deva-Latn-iast-1912", - "distance": 9 - }, - { - "mapName": "var-san-Deva-Latn-iast-1912", - "distance": 9 - }, - { - "mapName": "hk-yue-Hani-Latn-1888", - "distance": 13 - }, - { - "mapName": "var-zho-Hani-Latn-wd-1979", - "distance": 13 } ] }