Offline language identification for MoonBit, ported from franc-min
///|
test {
let d = @mbglot.detect(
"Olá, gostaria de mudar o endereço de entrega do meu pedido antes do envio.",
).unwrap()
assert_eq(d.lang, @mbglot.Por) // franc-min alone says Spanish
assert_eq(d.lang.bcp47(), "pt")
assert_true(d.reliable)
}| Held-out benchmark | franc-min's ranking | mbglot (default) |
|---|---|---|
| Accuracy, Tatoeba test half (2,761 texts) | 77.2% | 83.1% |
| Accuracy, FLORES-200 devtest (2,720 texts) | 89.2% | 92.6% |
| Answered as reliable, Tatoeba test half | 39.0% | 60.1% |
moon add cn-nnzy/mbglotimport {
"cn-nnzy/mbglot" @mbglot,
}$ moon run cmd/main -- "Hola, mi pedido llegó incompleto y falta una de las piezas."
language: Spanish (spa)
script: Latin
confidence: 0.506 (reliable)
runner-up: Italian (ita), margin 0.226
top 5:
1. spa Spanish 1.000
2. ita Italian 0.774
3. por Portuguese 0.702
4. lin Lingala 0.689
5. fra French 0.687$ moon run cmd/main -- --segments "Здравствуйте, заказ не пришёл. Hi, could you check the tracking number for order 4471 please? 谢谢,麻烦尽快处理一下这个问题。"
[0, 31) rus Russian reliable Здравствуйте, заказ не пришёл.
[31, 94) eng English reliable Hi, could you check the tracking numb...
[94, 110) cmn Mandarin Chinese reliable 谢谢,麻烦尽快处理一下这个问题。$ moon run cmd/main -- --file messages.txt
1 eng 0.680 reliable
2 - - -
3 fra 0.263 reliable
4 bos 0.040 not reliable$ moon run cmd/main -- --json --top 2 "Olá, gostaria de mudar o endereço de entrega do meu pedido antes do envio."
{"detection":{"lang":"por","bcp47":"pt","name":"Portuguese","script":"Latin","confidence":0.428,"margin":0.068,"reliable":true,"runner_up":"spa"},"top":[{"lang":"por","score":1},{"lang":"spa","score":0.932}]}$ moon run examples/tickets
#1001 support-en (eng English)
#1002 support-es (spa Spanish)
#1003 support-fr (fra French)
#1004 support-de (deu German)
#1005 support-pt (por Portuguese)
#1006 support-ru (rus Russian)
#1007 support-zh (cmn Mandarin Chinese)
#1008 support-ja (jpn Japanese)
#1009 support-ar (arb Standard Arabic)
#1010 triage (tur Turkish, not reliable)
#1011 triage (no language)
#1012 triage (por Portuguese, not reliable)node tools/build_demo.mjs _site
python -m http.server -d _site///|
test {
assert_eq(@mbglot.Lang::from_bcp47("pt-BR"), Some(@mbglot.Por))
assert_eq(@mbglot.Lang::from_bcp47("zh-Hant-TW"), Some(@mbglot.Cmn))
assert_eq(@mbglot.Lang::from_bcp47("zh-yue"), None)
assert_eq(@mbglot.Cmn.bcp47(), "zh")
assert_eq(@mbglot.Ceb.bcp47(), "ceb")
assert_eq(@mbglot.Rus.native_name(), "русский")
}///|
test {
let text = "Hola, mi pedido llegó incompleto y falta una de las piezas."
let detection = @mbglot.detect(text).unwrap()
assert_eq(detection.lang, @mbglot.Spa)
assert_eq(detection.lang.name(), "Spanish")
assert_eq(detection.script, @mbglot.Latin)
assert_eq(@mbglot.detect("12:30 👍"), None)
}///|
test {
let options = @mbglot.Options::new(allow=[@mbglot.Eng, @mbglot.Por])
let ranked = @mbglot.rank("Hola, mi pedido llegó incompleto.", options~)
assert_eq(ranked.map(r => r.0), [@mbglot.Por, @mbglot.Eng])
}///|
test {
let detector = @mbglot.Detector::new(
options=@mbglot.Options::new(allow=[Eng, Fra, Deu]),
)
let langs = [
"Where is my parcel?", "Où est mon colis ?", "Wo ist mein Paket?",
].map(text => detector.detect(text).map(d => d.lang))
assert_eq(langs, [Some(Eng), Some(Fra), Some(Deu)])
}///|
test {
// franc's trigrams alone prefer Komi for these three Russian words.
let franc = @mbglot.Options::new(mode=Franc)
assert_eq(
@mbglot.detect("это мой дом", options=franc).map(d => d.lang),
Some(@mbglot.Koi),
)
assert_eq(
@mbglot.detect("это мой дом").map(d => d.lang),
Some(@mbglot.Rus),
)
}///|
test {
let text = "Здравствуйте! После оплаты подписка так и не активировалась, а деньги списались."
assert_eq(@mbglot.detect(text).map(d => d.lang), Some(@mbglot.Rus))
let franc = @mbglot.Options::new(mode=Franc)
assert_eq(
@mbglot.detect(text, options=franc).map(d => d.lang),
Some(@mbglot.Bul),
)
}///|
test {
let text = "我们公司的产品在“美味の店”有售,欢迎大家来购买。"
assert_eq(@mbglot.detect(text).map(d => d.lang), Some(@mbglot.Cmn))
let franc = @mbglot.Options::new(mode=Franc)
assert_eq(
@mbglot.detect(text, options=franc).map(d => d.lang),
Some(@mbglot.Jpn),
)
assert_eq(@mbglot.chinese_variant(text), Some(Simplified))
assert_eq(
@mbglot.chinese_variant("請幫忙查一下物流資訊,謝謝。"),
Some(Traditional),
)
}///|
test {
let text = "Il mio ordine non è arrivato " +
"https://www.example.com/the/order/status/page/for/customers/with/questions"
assert_eq(@mbglot.detect(text).map(d => d.lang), Some(@mbglot.Ita))
let raw = @mbglot.Options::new(strip_markup=false)
assert_eq(
@mbglot.detect(text, options=raw).map(d => d.lang),
Some(@mbglot.Eng),
)
}///|
test {
let s = @mbglot.strip_markup("Merci @dana <b>!</b>")
assert_eq(s.text(), "Merci ! ")
assert_eq(s.removed().map(r => r.kind), [Mention, HtmlTag, HtmlTag])
assert_eq(s.original_offset(6), 6) // the space for @dana
assert_eq(s.original_offset(9), 15) // the "!"
}///|
test {
let why = @mbglot.explain(
"Olá, gostaria de mudar o endereço de entrega do meu pedido antes do envio.",
)
assert_eq(why.steps, [Trigrams(Spa), RelatedLanguages(Por, ["ç", "o", "do"])])
assert_eq(why.trigrams[0].trigram, " do")
}$ moon run cmd/main -- --explain --top 0 "Olá, gostaria de mudar o endereço de entrega do meu pedido antes do envio."
language: Portuguese (por)
script: Latin
confidence: 0.428 (reliable)
runner-up: Spanish (spa), margin 0.068
why:
trigram statistics rank Spanish (spa) first
letters and words that set Portuguese (por) apart from a related language: ç, o, do
trigrams for por over spa: " do" +233, "ido" +103, "o e" +101, "do " +87, "ntr" +51
trigrams for spa over por: "ere" -290, "der" -286, " en" -269, "ant" -145, "ari" -86///|
test {
let text = "Hello, my order has not arrived yet and I need it for Friday. " +
"Hola, mi pedido no ha llegado todavía y lo necesito para el viernes."
let parts = @mbglot.segments(text)
assert_eq(parts.map(s => s.detection.unwrap().lang), [
@mbglot.Eng,
@mbglot.Spa,
])
assert_eq(parts[1].start, 62)
}///|
test {
let esperanto = @mbglot.Profile::train(
"epo",
"Saluton, mia nomo estas Petro kaj mi loĝas en granda urbo. " +
"Mi tre ŝatas legi librojn kaj promeni en la parko kun mia hundo. " +
"Hodiaŭ la vetero estas bela, do ni iros al la maro. " +
"Ĉu vi volas veni kun ni? Ni manĝos fiŝojn kaj trinkos kafon.",
)
let profiles = [@mbglot.Eng, @mbglot.Spa, @mbglot.Ita].map(lang => {
@mbglot.Profile::of_lang(lang).unwrap()
})
profiles.push(esperanto)
let classifier = @mbglot.Classifier::new(profiles)
assert_eq(
classifier.classify("Ĉu vi scias, kie estas la stacidomo?"),
Some("epo"),
)
// A profile saves to plain text and reads back.
let saved = esperanto.save()
assert_eq(@mbglot.Profile::parse(saved).trigrams(), esperanto.trigrams())
}| Profiles | Languages | Samples | Accuracy | Macro F1 |
|---|---|---|---|---|
| franc-min | 39 | 1469 | 70.1% | 66.4% |
| trained on the dev half | 39 | 1469 | 84.8% | 80.9% |
///|
test {
let chat = @mbglot.Accumulator::new()
chat.feed("ok")
assert_eq(chat.detect(), None)
chat.feed("Hola, ¿cómo estás?")
chat.feed("Muy bien, gracias. Tengo una pregunta sobre mi pedido.")
assert_eq(chat.detect().map(d => d.lang), Some(@mbglot.Spa))
}///|
test {
assert_eq(
@full.franc("Bon dia, voldria saber quan arribarà la meva comanda."),
"cat",
)
assert_eq(
@full.franc(
"שלום, ההזמנה שלי עדיין לא הגיעה. אפשר לבדוק?",
),
"heb",
)
assert_eq(@full.franc_all("hi"), [("und", 1.0)])
assert_eq(@full.languages().length(), 178)
}///|
test {
assert_eq(
@mbglot.detect_script("Привет, мир"),
Some(@mbglot.Cyrillic),
)
assert_eq(@mbglot.detect_script("12:30 👍"), None)
}///|
test {
let samples : Array[@eval.Sample] = [
{
lang: Eng,
text: "Thanks for the quick reply, the replacement arrived today.",
},
{
lang: Deu,
text: "Vielen Dank, die Ersatzlieferung ist heute angekommen.",
},
]
let report = @eval.evaluate(samples, text => {
match @mbglot.detect(text) {
Some(d) => Some(d.lang)
None => None
}
})
assert_eq(report.total(), 2)
assert_eq(report.count(Deu, Some(Deu)), 1)
}| Length | Mode | Samples | Accuracy | Group accuracy | Macro F1 | Reliable precision | Coverage |
|---|---|---|---|---|---|---|---|
| short | franc | 1027 | 64.8% | 70.2% | 63.0% | 92.6% | 2.6% |
| short | enhanced | 1027 | 71.7% | 76.8% | 70.3% | 97.1% | 36.6% |
| medium | franc | 911 | 80.2% | 85.7% | 80.2% | 96.5% | 62.8% |
| medium | enhanced | 911 | 85.7% | 90.7% | 85.9% | 98.5% | 68.1% |
| long | franc | 823 | 89.2% | 95.7% | 89.8% | 99.4% | 58.1% |
| long | enhanced | 823 | 94.5% | 98.4% | 95.3% | 99.2% | 80.6% |
| all | franc | 2761 | 77.2% | 82.9% | 73.8% | 97.7% | 39.0% |
| all | enhanced | 2761 | 83.1% | 87.8% | 79.9% | 98.5% | 60.1% |
| Corpus | Mode | Samples | Accuracy | Group accuracy | Macro F1 | Reliable precision | Coverage |
|---|---|---|---|---|---|---|---|
| flores | franc | 2720 | 89.2% | 95.7% | 88.6% | 99.4% | 63.2% |
| flores | enhanced | 2720 | 92.6% | 97.9% | 92.4% | 99.6% | 76.5% |
| Backend | detect, Mode::Franc | detect, Mode::Enhanced | segments |
|---|---|---|---|
| wasm-gc | 123,000 texts/s | 66,800 texts/s | 2,070 texts/s |
| js | 55,400 texts/s | 35,700 texts/s | 960 texts/s |
moon update # once: fetches the registry index for moonbitlang/x
moon fmt
moon info
moon check --target all
moon test --target wasm
moon test --target wasm-gc
moon test --target js # needs Node.js
moon test --target native # needs a C compilernpm install --prefix "$TMP/franc" --no-save --ignore-scripts franc-min@6.2.0
curl -fsSL -o "$TMP/fixtures.js" https://raw.githubusercontent.com/wooorm/franc/3f9f0b51a96c5df32a407dad865a3011ac0fa2d1/test/fixtures.js
node tools/gen_golden.mjs "$TMP/franc/node_modules/franc-min" "$TMP/fixtures.js" golden_wbtest.mbt
moon fmtpub struct Accumulator {
// private fields
}pub struct Classifier {
// private fields
}pub struct Detector {
// private fields
}pub(all) struct Explanation {
detection : Detection?
steps : Array[Step]
trigrams : Array[TrigramScore]
}pub(all) enum Lang {
Spa
Eng
Por
Ind
Fra
Deu
Jav
Vie
Ita
Tur
Pol
Swh
Sun
Ron
Hau
Fuv
Bos
Hrv
Nld
Srp
Ckb
Yor
Uzn
Zlm
Ibo
Ceb
Tgl
Hun
Azj
Ces
Run
Plt
Qug
Mad
Nya
Zyb
Kin
Zul
Swe
Lin
Som
Hms
Hnj
Ilo
Rus
Ukr
Koi
Bel
Bul
Kaz
Arb
Urd
Pes
Skr
Pbu
Hin
Mar
Mai
Bho
Npi
Mag
Cmn
Ben
Jpn
Kor
Tel
Tam
Guj
Kan
Mal
Mya
Pan
Amh
Tha
Sin
Ell
} derive(Eq, Hash, Debug)pub struct Profile {
// private fields
}let reliable_min_confidence : Doublelet reliable_min_length : Intlet reliable_min_margin : Doublelet short_reliable_min_confidence : Doublelet short_text_length : IntInstall
Download zipOffline language identification for MoonBit, ported from franc-min