90 行
2.5 KiB
TypeScript
90 行
2.5 KiB
TypeScript
import { Children, cloneElement, Fragment, isValidElement } from 'react'
|
|
|
|
import { cn } from '@/lib/utils'
|
|
|
|
import type { ReactNode } from 'react'
|
|
|
|
|
|
export const formatUIString = (text: string, language: string): string =>
|
|
(language === 'ain' || language === 'ain-Kana')
|
|
? text.replace (/[ \t\r\n]+/g, '\u3000')
|
|
: text
|
|
|
|
|
|
const splitAinuText = (text: string): string[] => {
|
|
const parts: string[] = []
|
|
let part = ''
|
|
let boundary = false
|
|
for (const character of text)
|
|
{
|
|
// Keep punctuation runs and closing brackets/quotes with the preceding word.
|
|
if (boundary && !/[\u3000、,,。.\p{Pe}\p{Pf}]/u.test (character))
|
|
{
|
|
parts.push (part)
|
|
part = ''
|
|
boundary = false
|
|
}
|
|
part += character
|
|
if (/[\u3000、,,。.]/u.test (character))
|
|
boundary = true
|
|
}
|
|
if (part !== '')
|
|
parts.push (part)
|
|
return parts
|
|
}
|
|
|
|
|
|
export const localiseUIChildren = (children: ReactNode, language: string): ReactNode => {
|
|
if (language !== 'ain' && language !== 'ain-Kana')
|
|
return children
|
|
|
|
return Children.map (children, child => {
|
|
if (typeof child === 'string')
|
|
{
|
|
const parts = splitAinuText (formatUIString (child, language))
|
|
return parts.map ((part, index) => {
|
|
const spaces = part.match (/\u3000+$/u)?.[0] ?? ''
|
|
const word = part.slice (0, part.length - spaces.length)
|
|
return (
|
|
<Fragment key={index}>
|
|
{word !== '' && <span className="ainu-ui-word">{word}</span>}
|
|
{spaces}
|
|
{index < parts.length - 1 && <wbr/>}
|
|
</Fragment>)
|
|
})
|
|
}
|
|
|
|
if (isValidElement<{ children?: ReactNode; lang?: string }> (child))
|
|
{
|
|
// Only traverse inline formatting owned by the renderer. Components,
|
|
// links and code retain their own children and behaviour.
|
|
const inline = child.type === Fragment
|
|
|| (typeof child.type === 'string'
|
|
&& ['span', 'em', 'strong', 'del', 's', 'b', 'i', 'u',
|
|
'small', 'mark', 'sup', 'sub'].includes (child.type))
|
|
if (!inline
|
|
|| (child.props.lang != null && !(child.props.lang.startsWith ('ain'))))
|
|
return child
|
|
return cloneElement (child, { }, localiseUIChildren (child.props.children, language))
|
|
}
|
|
|
|
return child
|
|
})
|
|
}
|
|
|
|
|
|
const LocalisedText = (
|
|
{ children, language, className }: {
|
|
children: ReactNode
|
|
language: string
|
|
className?: string },
|
|
) => (
|
|
<span lang={language === 'ain' ? 'ain-Kana' : language}
|
|
className={cn ((language === 'ain' || language === 'ain-Kana') && 'ainu-ui-text',
|
|
className)}>
|
|
{localiseUIChildren (children, language)}
|
|
</span>)
|
|
|
|
|
|
export default LocalisedText
|