react-furigana
Version:
A simple React component for handling Japanese text with furigana.
1 lines • 7.31 kB
Source Map (JSON)
{"version":3,"file":"react-furigana.umd.cjs","sources":["../src/components/Ruby.tsx","../src/components/TokenDisplay.tsx","../src/tokenizer.ts","../src/ReactFurigana.tsx"],"sourcesContent":["type RubyProps = {\r\n kanji: string,\r\n furigana: string\r\n}\r\n\r\nexport default function Ruby(props: RubyProps) {\r\n return (\r\n <ruby>\r\n {props.kanji}\r\n <rt>\r\n {props.furigana}\r\n </rt>\r\n </ruby>\r\n );\r\n}\r\n","import type {Token} from '../tokenizer';\r\nimport Ruby from './Ruby';\r\n\r\ntype TokenDisplayProps = {\r\n token: Token;\r\n}\r\n\r\nexport default function TokenDisplay(props: TokenDisplayProps) {\r\n\r\n if (props.token.furigana) {\r\n return <Ruby kanji={props.token.kanji} furigana={props.token.furigana}/>;\r\n }\r\n\r\n return props.token.kanji;\r\n\r\n}\r\n","export type Token = {\r\n kanji: string,\r\n furigana?: string\r\n}\r\n\r\nexport class Tokenizer {\r\n\r\n //Source: http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml\r\n private static readonly hiraganaRanges = [[0x3040, 0x309F]];\r\n private static readonly punctuationsMarksRanges = [[0x3000, 0x3004], [0x3006, 0x303F], [0xFF00, 0xFF0F],\r\n [0xFF1A, 0xFF65], [0xFFA0, 0xFFEF]];\r\n\r\n private readonly text: string;\r\n private readonly separator: string;\r\n private readonly leftBracket: string;\r\n private readonly rightBracket: string;\r\n\r\n private readonly tokens: Token[] = [];\r\n private currentKanji = '';\r\n private currentFurigana: string | undefined;\r\n\r\n constructor(text: string, separator: string = ' ', leftBracket: string = '[', rightBracket: string = ']') {\r\n this.text = text;\r\n this.separator = separator;\r\n this.leftBracket = leftBracket;\r\n this.rightBracket = rightBracket;\r\n }\r\n\r\n parse() {\r\n if (this.tokens.length > 0) {\r\n return this.tokens;\r\n }\r\n\r\n for (const character of this.text) {\r\n if (this.currentFurigana === undefined) {\r\n this.parseKanji(character);\r\n } else {\r\n this.parseFurigana(character);\r\n }\r\n }\r\n\r\n this.addToken();\r\n\r\n return this.tokens;\r\n }\r\n\r\n private parseKanji(character: string) {\r\n if (character === this.leftBracket) {\r\n this.currentFurigana = '';\r\n } else if (character === this.separator) {\r\n this.addToken();\r\n } else {\r\n this.currentKanji += character;\r\n if (!Tokenizer.allowsFurigana(character)) {\r\n this.addToken();\r\n }\r\n }\r\n }\r\n\r\n private parseFurigana(character: string) {\r\n if (character === this.rightBracket) {\r\n this.addToken();\r\n } else {\r\n this.currentFurigana += character;\r\n }\r\n }\r\n\r\n private static allowsFurigana(character: string) {\r\n const unicode = character.charCodeAt(0);\r\n return !Tokenizer.isHiragana(unicode) && !Tokenizer.isPunctuationMark(unicode);\r\n }\r\n\r\n private static isHiragana(unicode: number) {\r\n return Tokenizer.isInRange(unicode, Tokenizer.hiraganaRanges);\r\n }\r\n\r\n private static isPunctuationMark(unicode: number) {\r\n return Tokenizer.isInRange(unicode, Tokenizer.punctuationsMarksRanges);\r\n }\r\n\r\n private static isInRange(unicode: number, ranges: number[][]) {\r\n return ranges.some(range => unicode >= range[0] && unicode <= range[1]);\r\n }\r\n\r\n private addToken() {\r\n if (!this.currentKanji) {\r\n return;\r\n }\r\n\r\n this.tokens.push({\r\n kanji: this.currentKanji,\r\n furigana: this.currentFurigana\r\n });\r\n this.currentKanji = '';\r\n this.currentFurigana = undefined;\r\n }\r\n\r\n}\r\n","import React from 'react';\r\nimport TokenDisplay from './components/TokenDisplay';\r\nimport type {Token} from './tokenizer';\r\nimport {Tokenizer} from './tokenizer';\r\n\r\ntype ReactFuriganaProps = {\r\n text: string,\r\n separator?: string,\r\n leftBracket?: string,\r\n rightBracket?: string,\r\n render?: (token: Token) => React.ReactNode\r\n}\r\n\r\nexport function ReactFurigana(props: ReactFuriganaProps) {\r\n\r\n const tokens = React.useMemo(() => {\r\n return new Tokenizer(props.text, props.separator, props.leftBracket, props.rightBracket).parse();\r\n }, [props.text, props.separator, props.leftBracket, props.rightBracket]);\r\n\r\n const render = React.useCallback((token: Token) => {\r\n return props.render ? props.render(token) : <TokenDisplay token={token}/>\r\n }, [props.render]);\r\n\r\n return (\r\n <>\r\n {tokens.map((token, index) => (\r\n <React.Fragment key={index}>\r\n {render(token)}\r\n </React.Fragment>\r\n ))}\r\n </>\r\n );\r\n\r\n}\r\n"],"names":["Ruby","props","jsx","TokenDisplay","Tokenizer","text","separator","leftBracket","rightBracket","character","unicode","ranges","range","ReactFurigana","tokens","React","render","token","Fragment","index"],"mappings":"qUAKA,SAAwBA,EAAKC,EAAkB,CAC3C,cACK,OAAA,CACI,SAAA,CAAAA,EAAM,MACPC,EAAAA,IAAC,KAAA,CACI,SAAAD,EAAM,QAAA,CACX,CAAA,EACJ,CAER,CCPA,SAAwBE,EAAaF,EAA0B,CAE3D,OAAIA,EAAM,MAAM,SACLC,MAACF,GAAK,MAAOC,EAAM,MAAM,MAAO,SAAUA,EAAM,MAAM,QAAA,CAAS,EAGnEA,EAAM,MAAM,KAEvB,CCVO,MAAMG,CAAU,CAGnB,OAAwB,eAAiB,CAAC,CAAC,MAAQ,KAAM,CAAC,EAC1D,OAAwB,wBAA0B,CAAC,CAAC,MAAQ,KAAM,EAAG,CAAC,MAAQ,KAAM,EAAG,CAAC,MAAQ,KAAM,EAClG,CAAC,MAAQ,KAAM,EAAG,CAAC,MAAQ,KAAM,CAAA,EAEpB,KACA,UACA,YACA,aAEA,OAAkB,CAAA,EAC3B,aAAe,GACf,gBAER,YAAYC,EAAcC,EAAoB,IAAKC,EAAsB,IAAKC,EAAuB,IAAK,CACtG,KAAK,KAAOH,EACZ,KAAK,UAAYC,EACjB,KAAK,YAAcC,EACnB,KAAK,aAAeC,CACxB,CAEA,OAAQ,CACJ,GAAI,KAAK,OAAO,OAAS,EACrB,OAAO,KAAK,OAGhB,UAAWC,KAAa,KAAK,KACrB,KAAK,kBAAoB,OACzB,KAAK,WAAWA,CAAS,EAEzB,KAAK,cAAcA,CAAS,EAIpC,YAAK,SAAA,EAEE,KAAK,MAChB,CAEQ,WAAWA,EAAmB,CAC9BA,IAAc,KAAK,YACnB,KAAK,gBAAkB,GAChBA,IAAc,KAAK,UAC1B,KAAK,SAAA,GAEL,KAAK,cAAgBA,EAChBL,EAAU,eAAeK,CAAS,GACnC,KAAK,SAAA,EAGjB,CAEQ,cAAcA,EAAmB,CACjCA,IAAc,KAAK,aACnB,KAAK,SAAA,EAEL,KAAK,iBAAmBA,CAEhC,CAEA,OAAe,eAAeA,EAAmB,CAC7C,MAAMC,EAAUD,EAAU,WAAW,CAAC,EACtC,MAAO,CAACL,EAAU,WAAWM,CAAO,GAAK,CAACN,EAAU,kBAAkBM,CAAO,CACjF,CAEA,OAAe,WAAWA,EAAiB,CACvC,OAAON,EAAU,UAAUM,EAASN,EAAU,cAAc,CAChE,CAEA,OAAe,kBAAkBM,EAAiB,CAC9C,OAAON,EAAU,UAAUM,EAASN,EAAU,uBAAuB,CACzE,CAEA,OAAe,UAAUM,EAAiBC,EAAoB,CAC1D,OAAOA,EAAO,KAAKC,GAASF,GAAWE,EAAM,CAAC,GAAKF,GAAWE,EAAM,CAAC,CAAC,CAC1E,CAEQ,UAAW,CACV,KAAK,eAIV,KAAK,OAAO,KAAK,CACb,MAAO,KAAK,aACZ,SAAU,KAAK,eAAA,CAClB,EACD,KAAK,aAAe,GACpB,KAAK,gBAAkB,OAC3B,CAEJ,CCpFO,SAASC,EAAcZ,EAA2B,CAErD,MAAMa,EAASC,EAAM,QAAQ,IAClB,IAAIX,EAAUH,EAAM,KAAMA,EAAM,UAAWA,EAAM,YAAaA,EAAM,YAAY,EAAE,MAAA,EAC1F,CAACA,EAAM,KAAMA,EAAM,UAAWA,EAAM,YAAaA,EAAM,YAAY,CAAC,EAEjEe,EAASD,EAAM,YAAaE,GACvBhB,EAAM,OAASA,EAAM,OAAOgB,CAAK,EAAIf,MAACC,GAAa,MAAAc,EAAa,EACxE,CAAChB,EAAM,MAAM,CAAC,EAEjB,OACIC,EAAAA,IAAAgB,EAAAA,SAAA,CACK,SAAAJ,EAAO,IAAI,CAACG,EAAOE,IAChBjB,EAAAA,IAACa,EAAM,SAAN,CACI,SAAAC,EAAOC,CAAK,GADIE,CAErB,CACH,EACL,CAGR"}