Treat language guesses as fallible classifications
documents
language identification
workforce research
Learn how two language detectors behave on short Riverton text samples and why abstention can be safer than a wrong route.
Short notes in several languages create a routing problem before content labels begin. The coordinator wants to route each note to the right review queue before anyone labels its content.
Open house. exposes the problem. A person reads the two-word notice as English, while a language detector may have too little evidence to say.
Language identification is a classification problem. A classifier assigns a label from a fixed set, such as en for English or es for Spanish. The risk has two shapes: a detector can return the wrong code, or it can decline to answer and leave the item unrouted. This lesson shows which shape these two detectors take on a tiny teaching file.
Note
The Riverton inbox and the language samples are fictional teaching materials.
TipWhat you will learn
Use this lesson to practice how to:
run two local language detectors on fixed samples;
compare detector output with recorded labels;
separate agreement from correctness;
explain why short texts are hard to classify; and
describe why returning NA can be safer than a wrong language code.
Load the labelled samples
The sample table is small, but the setup still records detector versions, checks text length, runs the local CLD2 and CLD3 detectors, and fingerprints the file. The samples are short strings written by the author for teaching.
knitr::kable( display_samples |>select(sample_id, written_in, display_text),col.names =c("Sample", "Recorded language", "Text"),caption ="Eight labelled language samples used for detection",row.names =FALSE,escape =FALSE)
Eight labelled language samples used for detection
Sample
Recorded language
Text
L01
en
Evening shifts require a valid forklift certification.
L02
en
Apply by October 15.
L03
es
Muy útil y fácil de usar.
L04
fr
Les cours du soir commencent en octobre.
L05
de
Der Kurs beginnt im Oktober.
L06
nl
De cursus begint in oktober.
L07
en
Open house.
L08
es
Casa abierta.
The written_in column records the language the author meant to write, not a verified annotation. The Spanish Casa abierta. is grammatical, but as an event notice it is a calque rather than an idiomatic phrase. These results come from the detector versions printed above; a package change can return different codes for the same strings.
Run two detectors
CLD2 and CLD3 are trained language detectors. NA means the detector returned no language label. They do not understand the whole situation around a note; they classify the characters they receive.
Evening shifts require a valid forklift certification.
54
7
en
en
L02
en
Apply by October 15.
20
4
en
en
L03
es
Muy útil y fácil de usar.
25
6
es
es
L04
fr
Les cours du soir commencent en octobre.
40
7
fr
fr
L05
de
Der Kurs beginnt im Oktober.
28
5
de
de
L06
nl
De cursus begint in oktober.
28
5
NA
NA
L07
en
Open house.
11
2
NA
NA
L08
es
Casa abierta.
13
2
NA
NA
For this installed version, both detectors abstain on Open house. by returning NA. That is a useful failure for routing, because the item can be sent to human review rather than routed under a wrong code without review.
Count matches, abstentions, and label differences
Agreement between two detectors does not prove correctness. They can share training data patterns and make the same mistake. The recorded labels let the team count what happened on these eight examples.
detector_counts <-tibble(detector =c("CLD2", "CLD3"),matched_recorded_label =c(sum(detected_samples$cld2_match),sum(detected_samples$cld3_match) ),no_label =c(sum(is.na(detected_samples$cld2_language)),sum(is.na(detected_samples$cld3_language)) )) |>mutate(differed_from_recorded_label =nrow(detected_samples) - matched_recorded_label - no_label )knitr::kable( detector_counts,col.names =c("Detector","Matched recorded label","No label","Different from recorded label" ),caption ="Detector outcomes across the eight labelled samples",row.names =FALSE)
Detector outcomes across the eight labelled samples
Detector
Matched recorded label
No label
Different from recorded label
CLD2
5
3
0
CLD3
5
3
0
Each detector matches 5 of the 8 recorded labels, returns no label for 3 of the 8, and differs from the recorded label on 0 of the 8. Those fractions describe this tiny teaching file. Eight samples measure nothing about detector accuracy on a workforce archive.
Compare short and longer text
Length changes the amount of evidence a detector sees, but this file is too small to estimate a length effect. There is no principled cutoff here. The 13-character line separates the two shortest strings from the other 6 examples.
length_summary <- detected_samples |>group_by(length_group) |>summarise(samples =n(),cld2_matches =sum(cld2_match),cld2_no_label =sum(is.na(cld2_language)),cld3_matches =sum(cld3_match),cld3_no_label =sum(is.na(cld3_language)),.groups ="drop" ) |>arrange(length_group)knitr::kable( length_summary,col.names =c("Length group","Samples","CLD2 matches","CLD2 no label","CLD3 matches","CLD3 no label" ),caption ="Detector outcomes by sample length in this file",row.names =FALSE)
Detector outcomes by sample length in this file
Length group
Samples
CLD2 matches
CLD2 no label
CLD3 matches
CLD3 no label
long
6
5
1
5
1
short
2
0
2
0
2
The two shortest samples produce no matches because both detectors abstain. Among the six other samples, each detector matches 5 recorded labels and abstains on 1. The Dutch sentence is long enough to read by eye, but both detectors return NA.
A wrong code is worse than no code
The eight labelled samples produced no wrong codes. That does not mean wrong codes are impossible. The next string was chosen because it produces one: it is a single hand-picked case, not a rate, and it shows what a wrong code looks like. It also sits just above the arbitrary 13-character cutoff.
Two short English probes and their detected languages
Text
Recorded language
CLD2
CLD3
Open house.
en
NA
NA
Evening class.
en
NA
lb
Evening class. is English in this teaching probe. CLD2 returns NA, while CLD3 returns lb, the ISO 639-1 code for Luxembourgish. For routing, the abstention is safer: no queue is selected until someone checks the item.
These eight samples are all written in the Latin alphabet, so this page shows nothing about other writing systems. That is a limit of the file, not a reassurance.
Language identification in production
While cld2 and cld3 illustrate the routing problem, models such as Meta’s fasttext are common alternatives for fast language identification. When deploying any model, keep these production realities in mind:
Language scope and BCP 47: Projects must explicitly state their supported language scope upfront. When recording a language, use a standard like BCP 47 tags (e.g., en-US) rather than informal names.
Per-language recall: A model’s overall accuracy hides its failures. Always measure recall per language, because models routinely fail on low-resource languages.
Code-switching: The single-label cld2::detect_language() and cld3::detect_language() calls used here return one document label. Mixed language text needs a detector designed for spans or tokens, or a review route; single-label behavior should not be generalized to every model.
Probability vs. certainty: A model might return a confidence score like 0.95, but do not treat this as a calibrated probability. It is only the model’s internal score, not a statistical guarantee.
What to remember
Language identification may return a code or no label.
Agreement between detectors does not prove that a language code is correct.
The eight-row file is an inspection exercise: both detectors declined to label the two shortest strings rather than guessing at them.
Returning NA can be safer than sending text to the wrong language queue.
Items with NA results belong in review, and short-text language codes should prompt checking rather than settle a queue. The hand-picked probe demonstrates a possible wrong code; the eight-row file demonstrates safe abstention.