Replace graphemer with unicode-segmenter (#9526)

* replace graphemer with unicode-segmenter

* use grapheme entrypoint

* force resolution of unicode-segmenter
This commit is contained in:
Samuel Newman
2025-12-30 13:39:09 +02:00
committed by GitHub
parent 82877a096d
commit 9743149c26
11 changed files with 57 additions and 99 deletions
@@ -20,7 +20,7 @@ import {Text as TiptapText} from '@tiptap/extension-text'
import {generateJSON} from '@tiptap/html'
import {Fragment, Node, Slice} from '@tiptap/pm/model'
import {EditorContent, type JSONContent, useEditor} from '@tiptap/react'
import Graphemer from 'graphemer'
import {splitGraphemes} from 'unicode-segmenter/grapheme'
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
import {blobToDataUri, isUriImage} from '#/lib/media/util'
@@ -218,7 +218,7 @@ export function TextInput({
// all the lines get mushed together -sfn
'\n',
)
const graphemes = new Graphemer().splitGraphemes(textBefore)
const graphemes = [...splitGraphemes(textBefore)]
if (graphemes.length > 0) {
const lastGrapheme = graphemes[graphemes.length - 1]
@@ -1,36 +0,0 @@
import {useCallback, useMemo} from 'react'
import Graphemer from 'graphemer'
export const useGrapheme = () => {
const splitter = useMemo(() => new Graphemer(), [])
const getGraphemeString = useCallback(
(name: string, length: number) => {
let remainingCharacters = 0
if (name.length > length) {
const graphemes = splitter.splitGraphemes(name)
if (graphemes.length > length) {
remainingCharacters = 0
name = `${graphemes.slice(0, length).join('')}`
} else {
remainingCharacters = length - graphemes.length
name = graphemes.join('')
}
} else {
remainingCharacters = length - name.length
}
return {
name,
remainingCharacters,
}
},
[splitter],
)
return {
getGraphemeString,
}
}
@@ -4,7 +4,7 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {MAX_ALT_TEXT} from '#/lib/constants'
import {useEnforceMaxGraphemeCount} from '#/lib/strings/helpers'
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
import {LANGUAGES} from '#/locale/languages'
import {isWeb} from '#/platform/detection'
import {useLanguagePrefs} from '#/state/preferences'
@@ -72,7 +72,6 @@ function SubtitleDialogInner({
const control = Dialog.useDialogContext()
const {_} = useLingui()
const t = useTheme()
const enforceLen = useEnforceMaxGraphemeCount()
const {primaryLanguage} = useLanguagePrefs()
const [altText, setAltText] = useState(defaultAltText)
@@ -94,18 +93,23 @@ function SubtitleDialogInner({
const subtitleMissingLanguage = captions.some(sub => sub.lang === '')
const isOverMaxLength = isOverMaxGraphemeCount({
text: altText,
maxCount: MAX_ALT_TEXT,
})
return (
<Dialog.ScrollableInner label={_(msg`Video settings`)}>
<View style={a.gap_md}>
<Text style={[a.text_xl, a.font_semi_bold, a.leading_tight]}>
<Trans>Alt text</Trans>
</Text>
<TextField.Root>
<TextField.Root isInvalid={isOverMaxLength}>
<Dialog.Input
label={_(msg`Alt text`)}
placeholder={_(msg`Add alt text (optional)`)}
value={altText}
onChangeText={evt => setAltText(enforceLen(evt, MAX_ALT_TEXT))}
onChangeText={setAltText}
maxLength={MAX_ALT_TEXT * 10}
multiline
style={{maxHeight: 300}}
@@ -118,6 +122,18 @@ function SubtitleDialogInner({
/>
</TextField.Root>
{isOverMaxLength && (
<Text
style={[
a.text_md,
{color: t.palette.negative_500},
a.leading_snug,
a.mt_md,
]}>
<Trans>Alt text must be less than {MAX_ALT_TEXT} characters.</Trans>
</Text>
)}
{isWeb && (
<>
<View
@@ -173,7 +189,8 @@ function SubtitleDialogInner({
saveAltText(altText)
control.close()
}}
style={a.mt_lg}>
style={a.mt_lg}
disabled={isOverMaxLength}>
<ButtonText>
<Trans>Done</Trans>
</ButtonText>