Skip to content

Commit 28fce17

Browse files
committed
Nested formatting
1 parent be20ef5 commit 28fce17

File tree

4 files changed

+122
-3
lines changed

4 files changed

+122
-3
lines changed

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ You don't need to prepare any translation files, just provide your API key and t
1313
- 🎯 Dynamic parameter interpolation
1414
- 🔍 Persist translation tracking
1515
- 🔌 Offline mode support
16+
- 🎨 Nested text formatting support
1617
- ⚙️ Configurable cache TTL
1718
- ⚡️ Lightweight and efficient
1819

@@ -49,7 +50,7 @@ const App = () => {
4950

5051
### 2. Use the Translation Hook
5152

52-
Basic usage:
53+
**Basic usage:**
5354

5455
```typescript
5556
import { useAutoTranslate } from "react-native-autolocalise";
@@ -61,12 +62,35 @@ const MyComponent = () => {
6162
<div>
6263
<h1>{t("Welcome to our app!", false)}</h1>
6364
<p>{t("This text will be automatically translated")}</p>
65+
<p style={{ color: "black" }}>
66+
welcome
67+
<p style={{ color: "read" }}> to </p>our app
68+
</p>
6469
</div>
6570
);
6671
};
6772
```
6873

69-
Use with params:
74+
**Use with nested text formatting:**
75+
76+
```typescript
77+
import { useAutoTranslate, FormattedText } from "react-native-autolocalise";
78+
79+
const MyComponent = () => {
80+
const { t } = useAutoTranslate();
81+
82+
return (
83+
<FormattedText>
84+
<Text>
85+
Hello, we <Text style={{ color: "red" }}>want</Text> you to be{" "}
86+
<Text style={{ fontWeight: "bold" }}>happy</Text>!
87+
</Text>
88+
</FormattedText>
89+
);
90+
};
91+
```
92+
93+
**Use with params:**
7094

7195
```typescript
7296
import { useAutoTranslate } from "react-native-autolocalise";

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-native-autolocalise",
4-
"version": "0.1.0",
4+
"version": "0.2.0",
55
"description": "Auto-translation SDK for React Native and Expo applications",
66
"main": "dist/index.js",
77
"module": "dist/index.esm.js",

src/components/FormattedText.tsx

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

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ const autoTranslate = {
1313
return service.init();
1414
},
1515
};
16+
export { FormattedText } from "./components/FormattedText";
1617

1718
export default autoTranslate;

0 commit comments

Comments
 (0)