Intl helpers β
The four primitives the rest of the helpers are built on: which locale, how to split text into characters a reader would count, and one shared collator.
<script setup lang="ts">
import { collator, graphemeCount, graphemes, localeTag } from '@shared/utils/intl'
import { Badge } from '@shared/components/ui/badge'
import { Button } from '@shared/components/ui/button'
import { Input } from '@shared/components/ui/input'
import { Label } from '@shared/components/ui/label'
import { computed, ref } from 'vue'
const LOCALES = ['en', 'de', 'tr', 'ar']
const text = ref('Family π¨βπ©βπ§βπ¦ time')
const locale = ref('en')
const NAMES = ['Zurich', 'Γvila', 'Γ
kerman', 'Item 10', 'Item 9', 'apple']
const parts = computed(() => graphemes(text.value, locale.value))
const naive = computed(() => [...text.value])
const collated = computed(() => [...NAMES].sort(collator(locale.value, { numeric: true }).compare))
const naiveSort = computed(() => [...NAMES].sort())
</script>
<template>
<div class="flex flex-col gap-5">
<div class="grid gap-3 sm:grid-cols-3">
<div class="grid gap-1 sm:col-span-2">
<Label for="intl-text">Text</Label>
<Input id="intl-text" v-model="text" />
</div>
<div class="grid gap-1">
<span class="sa-text-sm font-medium">Locale</span>
<div class="flex flex-wrap items-center gap-2">
<Button
v-for="code in LOCALES"
:key="code"
size="sm"
variant="outline"
:aria-pressed="locale === code"
@click="locale = code"
>
{{ code }}
</Button>
</div>
</div>
</div>
<section class="flex flex-col gap-2">
<div class="flex flex-wrap gap-2">
<Badge variant="outline">
localeTag β {{ localeTag(locale) }}
</Badge>
<Badge variant="outline">
graphemeCount β {{ graphemeCount(text, locale) }}
</Badge>
<Badge variant="outline">
.length β {{ text.length }}
</Badge>
</div>
<p class="sa-text-sm">
<span class="font-semibold">graphemes:</span>
<span v-for="(part, i) in parts" :key="i" class="ms-1 rounded border border-border px-1">{{ part }}</span>
</p>
<p class="sa-text-sm text-muted-foreground">
<span class="font-semibold">spread:</span>
<span v-for="(part, i) in naive" :key="i" class="ms-1 rounded border border-border px-1">{{ part }}</span>
</p>
</section>
<section class="flex flex-col gap-2">
<span class="sa-text-sm font-semibold">collator against a plain sort</span>
<p class="sa-text-sm">
collator: {{ collated.join(' Β· ') }}
</p>
<p class="sa-text-sm text-muted-foreground">
plain: {{ naiveSort.join(' Β· ') }}
</p>
</section>
</div>
</template>Import β
import { collator, graphemeCount, graphemes, localeTag } from '@shared/utils/intl'localeTag β
function localeTag(locale?: string): stringA BCP-47 tag for an Intl.* constructor. Omit the argument for the app's active locale.
It takes a raw tag, not AvailableLocale. Pinning it to the nine shipped locales would make capitalize('istanbul', { locale: 'tr' }) impossible from an English interface, which is exactly the case that needs it.
The tag is passed through unchecked, so a malformed one reaches the Intl constructor and throws RangeError: Invalid language tag β including the empty string. Pass a tag you control, not one a user typed.
graphemes β
function graphemes(value: string, locale?: string): string[]Splits a string into what a reader would call characters.
graphemes('Family π¨βπ©βπ§βπ¦') // ['F','a','m','i','l','y',' ','π¨βπ©βπ§βπ¦']That last entry is one grapheme and eleven UTF-16 code units, which is why .split('') and .slice() break it.
graphemeCount β
function graphemeCount(value: string, locale?: string): numberThe length a reader would count. 'Family π¨βπ©βπ§βπ¦'.length is 18; graphemeCount says 8.
collator β
function collator(locale?: string, options?: Intl.CollatorOptions): Intl.CollatorA memoized Intl.Collator.
It is returned rather than applied, so callers hand .compare straight to sort:
const compare = collator().compare
const sorted = [...names].sort(compare)That matters for performance. a.localeCompare(b) inside a comparator builds a fresh collator for every one of the O(n log n) comparisons; this builds one and reuses it.
Instances are cached per locale-and-options pair.
Where they are used β
| Helper | Uses |
|---|---|
sortBy | collator |
truncate, initials, maskString | graphemes |
capitalize, titleCase | localeTag |
| Format helpers | localeTag |
You rarely call these directly. They are documented because they explain why the others behave the way they do.
Related β
- String helpers β the main consumer.
- Array helpers β
sortByand the collator. - Internationalization β where the active locale comes from.