Problem
Language tags in the ontology editor (annotations on classes, properties, individuals) are currently free-text <input> fields. Users can type any arbitrary string, which risks invalid or inconsistent language tags in the ontology (e.g. english instead of en, or en_US instead of en-US). Since language tags must conform to BCP 47, we should constrain input to valid codes only.
Affected Components
| Component |
File |
Current UI |
AnnotationRow |
components/editor/standard/AnnotationRow.tsx |
<input type="text"> (w-14) |
InlineAnnotationAdder |
components/editor/standard/InlineAnnotationAdder.tsx |
<input type="text"> (w-14) |
These are used in ClassDetailPanel, PropertyDetailPanel, IndividualDetailPanel, and AnnotationEditor for editing rdfs:label, rdfs:comment, skos:definition, and custom annotation properties.
Proposed Solution: Searchable Combobox
Since BCP 47 defines hundreds of valid language codes, a plain <select> dropdown would be unwieldy. Instead, implement a searchable combobox (typeahead select) pattern:
UI Behavior
- Compact trigger — displays the current language code (e.g.
en) plus flag emoji, same width as the current input (~w-14)
- Click/focus opens a popover with a search input and scrollable list of language codes
- Search filters by code (
en, fr-CA) and by display name (English, French (Canada))
- Selection closes the popover and updates the language tag
- Keyboard — arrow keys navigate, Enter selects, Escape closes, typing filters
Implementation Plan
1. Add dependencies
cmdk (Command Menu) — lightweight, composable, accessible combobox primitive
@radix-ui/react-popover — already using Radix UI in the project
2. Create shared LanguagePicker component
components/editor/LanguagePicker.tsx — a self-contained combobox that:
- Accepts
value: string and onChange: (code: string) => void
- Renders the current code + flag emoji as a compact button trigger
- Opens a Popover with a
cmdk Command list of language options
- Supports keyboard navigation and search filtering
3. Create language code data module
lib/i18n/languageCodes.ts — curated list of BCP 47 codes with metadata:
export interface LanguageOption {
code: string; // BCP 47 code, e.g. "en", "fr-CA"
name: string; // English name, e.g. "English", "French (Canada)"
nativeName: string; // Native name, e.g. "English", "Français (Canada)"
}
- Start with ~80-100 most commonly used language codes (ISO 639-1 base languages + major regional variants)
- Organize with common languages (en, fr, de, es, it, pt, la, etc.) pinned to the top as "frequently used"
- Can be expanded over time; the combobox search makes a large list manageable
4. Replace text inputs
- In
AnnotationRow.tsx: replace the <input type="text"> (lines 70-78) with <LanguagePicker>
- In
InlineAnnotationAdder.tsx: replace the <input type="text"> (lines 203-211) with <LanguagePicker>
5. Consolidate LANG_TO_COUNTRY mapping
The existing LANG_TO_COUNTRY map in lib/utils.ts (lines 107-145) and langToFlag() can be consolidated with the new language code data module to avoid duplication.
Wireframe
┌──────────────────────────────────────────────────┐
│ rdfs:label [Hello World ] 🇺🇸 [en ▾] │ ← compact trigger
└──────────────────────────────────────────────────┘
│
┌─────▼──────────────┐
│ 🔍 Search... │
├────────────────────┤
│ Frequently used │
│ 🇺🇸 en English │
│ 🇫🇷 fr French │
│ 🇩🇪 de German │
│ 🇪🇸 es Spanish │
│ 🇮🇹 it Italian │
│ 🇻🇦 la Latin │
│ ─────────────────── │
│ All languages │
│ 🇸🇦 ar Arabic │
│ 🇧🇬 bg Bulgarian │
│ 🇨🇳 zh Chinese │
│ ... │
└────────────────────┘
Acceptance Criteria
Problem
Language tags in the ontology editor (annotations on classes, properties, individuals) are currently free-text
<input>fields. Users can type any arbitrary string, which risks invalid or inconsistent language tags in the ontology (e.g.englishinstead ofen, oren_USinstead ofen-US). Since language tags must conform to BCP 47, we should constrain input to valid codes only.Affected Components
AnnotationRowcomponents/editor/standard/AnnotationRow.tsx<input type="text">(w-14)InlineAnnotationAddercomponents/editor/standard/InlineAnnotationAdder.tsx<input type="text">(w-14)These are used in
ClassDetailPanel,PropertyDetailPanel,IndividualDetailPanel, andAnnotationEditorfor editingrdfs:label,rdfs:comment,skos:definition, and custom annotation properties.Proposed Solution: Searchable Combobox
Since BCP 47 defines hundreds of valid language codes, a plain
<select>dropdown would be unwieldy. Instead, implement a searchable combobox (typeahead select) pattern:UI Behavior
en) plus flag emoji, same width as the current input (~w-14)en,fr-CA) and by display name (English,French (Canada))Implementation Plan
1. Add dependencies
cmdk(Command Menu) — lightweight, composable, accessible combobox primitive@radix-ui/react-popover— already using Radix UI in the project2. Create shared
LanguagePickercomponentcomponents/editor/LanguagePicker.tsx— a self-contained combobox that:value: stringandonChange: (code: string) => voidcmdkCommand list of language options3. Create language code data module
lib/i18n/languageCodes.ts— curated list of BCP 47 codes with metadata:4. Replace text inputs
AnnotationRow.tsx: replace the<input type="text">(lines 70-78) with<LanguagePicker>InlineAnnotationAdder.tsx: replace the<input type="text">(lines 203-211) with<LanguagePicker>5. Consolidate
LANG_TO_COUNTRYmappingThe existing
LANG_TO_COUNTRYmap inlib/utils.ts(lines 107-145) andlangToFlag()can be consolidated with the new language code data module to avoid duplication.Wireframe
Acceptance Criteria
langToFlag()logic)enfor new annotations