Reviewed-on: #433 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #433 でマージされました。
このコミットが含まれているのは:
@@ -0,0 +1,89 @@
|
||||
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
|
||||
新しいイシューから参照
ユーザーをブロックする