Skip to content

Commit 1a7220c

Browse files
authored
v0.2.0 Nested Formatting
1 parent bd488e1 commit 1a7220c

File tree

5 files changed

+134
-6
lines changed

5 files changed

+134
-6
lines changed

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ You don't need to prepare any translation files, just provide your API key and t
1111
- 🌐 React and Next.js support
1212
- 🚀 Automatic string translation
1313
- 🎯 Dynamic parameter interpolation
14-
- 🔍 Static translation tracking
14+
- 🔍 Persist translation tracking
15+
- 🎨 Nested text formatting support
1516
- ⚙️ Configurable cache TTL
1617
- ⚡️ Tree-shakeable and side-effect free
1718
- 🔄 Server-side rendering support
@@ -50,7 +51,7 @@ const App = () => {
5051

5152
### Use the Translation Hook
5253

53-
Basic usage:
54+
**Basic usage:**
5455

5556
```typescript
5657
import { useAutoTranslate } from "react-autolocalise";
@@ -67,7 +68,31 @@ const MyComponent = () => {
6768
};
6869
```
6970

70-
Use with params:
71+
**Use with nested text formatting:**
72+
73+
```typescript
74+
import React from "react";
75+
import { FormattedText } from "react-autolocalise";
76+
77+
const MyComponent = () => {
78+
return (
79+
<div>
80+
<FormattedText>
81+
<p>
82+
Hello, we <div style={{ color: "red" }}>want</div> you to be{" "}
83+
<span style={{ fontWeight: "bold" }}>happy</span>!
84+
</p>
85+
</FormattedText>
86+
<FormattedText persist={false}>
87+
Hello,
88+
<p style={{ color: "red" }}>World</p>
89+
</FormattedText>
90+
</div>
91+
);
92+
};
93+
```
94+
95+
**Use with params:**
7196

7297
```typescript
7398
import { useAutoTranslate } from "react-autolocalise";

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"type": "module",
33
"name": "react-autolocalise",
4-
"version": "0.1.0",
4+
"version": "0.2.0",
55
"description": "Auto-translation SDK for React and Next.js applications with SSR support",
66
"main": "dist/index.cjs",
77
"module": "dist/index.mjs",

src/components/FormattedText.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from "react";
2+
import { useAutoTranslate } from "../context/TranslationContext";
3+
4+
/**
5+
* FormattedText is a component that handles nested text formatting during translation.
6+
* It preserves styling and structure of nested text elements while allowing the content
7+
* to be translated.
8+
*
9+
* @example
10+
* ```tsx
11+
* <FormattedText>
12+
* Hello, <span style={{ color: 'red' }}>world</span>!
13+
* </FormattedText>
14+
* ```
15+
*/
16+
interface FormattedTextProps {
17+
children: React.ReactNode;
18+
style?: React.CSSProperties;
19+
/**
20+
* Whether to persist the text for review in the dashboard.
21+
* @default true
22+
*/
23+
persist?: boolean;
24+
}
25+
26+
export const FormattedText: React.FC<FormattedTextProps> = ({
27+
children,
28+
style,
29+
persist = true,
30+
}) => {
31+
const { t } = useAutoTranslate();
32+
33+
/**
34+
* Extracts text content and styled nodes from the children prop.
35+
* Converts nested span elements into a template format (e.g., <0>styled text</0>)
36+
* while preserving the original styled nodes for later restoration.
37+
*
38+
* @param nodes - The React nodes to process (typically the children prop)
39+
* @returns An object containing the template text and an array of styled nodes
40+
*/
41+
const extractTextAndStyles = (
42+
nodes: React.ReactNode
43+
): {
44+
text: string;
45+
styles: Array<{ node: React.ReactElement; text: string }>;
46+
} => {
47+
const styles: Array<{ node: React.ReactElement; text: string }> = [];
48+
let text = "";
49+
50+
const processNode = (node: React.ReactNode) => {
51+
if (typeof node === "string") {
52+
text += node;
53+
return;
54+
}
55+
56+
if (React.isValidElement(node)) {
57+
const children = node.props.children;
58+
if (typeof children === "string") {
59+
text += `<${styles.length}>${children}</${styles.length}>`;
60+
styles.push({ node, text: children });
61+
} else if (Array.isArray(children)) {
62+
children.forEach(processNode);
63+
} else if (children) {
64+
processNode(children);
65+
}
66+
}
67+
};
68+
69+
processNode(nodes);
70+
return { text, styles };
71+
};
72+
73+
/**
74+
* Restores the styled nodes in the translated text by replacing template markers
75+
* with the original styled components, but with translated content.
76+
*
77+
* @param translatedText - The translated text containing template markers
78+
* @param styles - Array of original styled nodes and their text content
79+
* @returns An array of React nodes with restored styling and translated content
80+
*/
81+
const restoreStyledText = (
82+
translatedText: string,
83+
styles: Array<{ node: React.ReactElement; text: string }>
84+
): React.ReactNode[] => {
85+
const parts = translatedText.split(/(<\d+>.*?<\/\d+>)/g);
86+
return parts.map((part, index) => {
87+
const match = part.match(/<(\d+)>(.*?)<\/\1>/);
88+
if (match) {
89+
const [, styleIndex, content] = match;
90+
const { node } = styles[parseInt(styleIndex)];
91+
return React.cloneElement(node, { key: `styled-${index}` }, content);
92+
}
93+
return part;
94+
});
95+
};
96+
97+
const { text, styles } = extractTextAndStyles(children);
98+
const translatedText = t(text, persist);
99+
100+
return <span style={style}>{restoreStyledText(translatedText, styles)}</span>;
101+
};

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ const autoTranslate = {
1818
},
1919
};
2020

21+
export { FormattedText } from "./components/FormattedText";
22+
2123
export default autoTranslate;

0 commit comments

Comments
 (0)