UNPKG

react-i18next

Version:

Internationalization for react done right. Using the i18next i18n ecosystem.

106 lines 3.85 kB
import React from 'react'; import { TranslationParserError } from './TranslationParserError.js'; import { tokenize } from './tokenizer.js'; import { decodeHtmlEntities } from './htmlEntityDecoder.js'; const renderDeclarationNode = (declaration, children, childDeclarations) => { const { type, props = {} } = declaration; if (props.children && Array.isArray(props.children) && childDeclarations) { const { children: _childrenToRemove, ...propsWithoutChildren } = props; return React.createElement(type, propsWithoutChildren, ...children); } if (children.length === 0) { return React.createElement(type, props); } if (children.length === 1) { return React.createElement(type, props, children[0]); } return React.createElement(type, props, ...children); }; export const renderTranslation = (translation, declarations = []) => { if (!translation) { return []; } const tokens = tokenize(translation); const result = []; const stack = []; const literalTagNumbers = new Set(); const getCurrentDeclarations = () => { if (stack.length === 0) { return declarations; } const parentFrame = stack[stack.length - 1]; if (parentFrame.declaration.props?.children && Array.isArray(parentFrame.declaration.props.children)) { return parentFrame.declaration.props.children; } return parentFrame.declarations; }; tokens.forEach(token => { switch (token.type) { case 'Text': { const decoded = decodeHtmlEntities(token.value); const targetArray = stack.length > 0 ? stack[stack.length - 1].children : result; targetArray.push(decoded); } break; case 'TagOpen': { const { tagNumber } = token; const currentDeclarations = getCurrentDeclarations(); const declaration = currentDeclarations[tagNumber]; if (!declaration) { literalTagNumbers.add(tagNumber); const literalText = `<${tagNumber}>`; const targetArray = stack.length > 0 ? stack[stack.length - 1].children : result; targetArray.push(literalText); break; } stack.push({ tagNumber, children: [], position: token.position, declaration, declarations: currentDeclarations }); } break; case 'TagClose': { const { tagNumber } = token; if (literalTagNumbers.has(tagNumber)) { const literalText = `</${tagNumber}>`; const literalTargetArray = stack.length > 0 ? stack[stack.length - 1].children : result; literalTargetArray.push(literalText); literalTagNumbers.delete(tagNumber); break; } if (stack.length === 0) { throw new TranslationParserError(`Unexpected closing tag </${tagNumber}> at position ${token.position}`, token.position, translation); } const frame = stack.pop(); if (frame.tagNumber !== tagNumber) { throw new TranslationParserError(`Mismatched tags: expected </${frame.tagNumber}> but got </${tagNumber}> at position ${token.position}`, token.position, translation); } const element = renderDeclarationNode(frame.declaration, frame.children, frame.declarations); const elementTargetArray = stack.length > 0 ? stack[stack.length - 1].children : result; elementTargetArray.push(element); } break; } }); if (stack.length > 0) { const unclosed = stack[stack.length - 1]; throw new TranslationParserError(`Unclosed tag <${unclosed.tagNumber}> at position ${unclosed.position}`, unclosed.position, translation); } return result; };