Skip to content

Commit

Permalink
chore(i18n): added support for custom markup
Browse files Browse the repository at this point in the history
  • Loading branch information
Fredx87 committed Jan 6, 2025
1 parent 6840432 commit 2619efa
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 12 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,32 @@ t("my-key", {
})
```

#### HTML Interpolation

Our translation system doesn't allow HTML tags in the translation strings, and it is recommended to use placeholders instead. For example, a string containing a link should be written like `This is a {{#a}}link{{/a}}` instead of `This is a <a>link</a>`, which is the default behavior of the `react-i18next` library. To achieve this, we use can use the `Trans` component provided by the `react-i18next`, using the [components prop](https://react.i18next.com/latest/trans-component#alternative-usage-which-lists-the-components-v11.6.0) and passing a custom post processor that replaces the placeholders with the actual HTML tags.

Example with a link:
```tsx
<Trans
i18nKey="my-key"
defaults="This is a link: {{#a}}Click here{{/a}}"
components={{
a: <Anchor href="https://example.com" target="_blank" isExternal />,
}}
tOptions={{ postProcess: "customMarkup" }}
/>
```

Example with a self-closing tag:
```tsx
<Trans
i18nKey="my-key"
defaults="This is an icon: {{#icon/}}"
components={{ icon: <MyIcon /> }}
tOptions={{ postProcess: "customMarkup" }}
/>
```

#### String extraction

The `bin/extract-strings.mjs` script can be used to extract translation strings from the source code and put them in the YAML file that is picked up by our internal translation system. The usage of the script is documented in the script itself.
Expand Down
38 changes: 38 additions & 0 deletions src/modules/shared/i18n/customMarkupPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { customMarkupPlugin } from "./customMarkupPlugin";

describe("customMarkupPlugin", () => {
it("should convert interpolation tags to HTML", () => {
const value = "{{#strong}}Hello{{/strong}} {{#br/}}";
expect(customMarkupPlugin.process(value, "my_key", {}, null)).toBe(
"<strong>Hello</strong> <br />"
);
});

it("should convert interpolation tags to HTML with multiple tags", () => {
const value = "{{#strong}}Hello{{/strong}} {{#br/}} {{#em}}World{{/em}}";
expect(customMarkupPlugin.process(value, "my_key", {}, null)).toBe(
"<strong>Hello</strong> <br /> <em>World</em>"
);
});

it("should not convert interpolation tags to HTML with nested tags", () => {
const value = "{{#strong}}Hello {{#em}}World{{/em}}{{/strong}}";
expect(customMarkupPlugin.process(value, "my_key", {}, null)).toBe(
"<strong>Hello {{#em}}World{{/em}}</strong>"
);
});

it("should not convert interpolation tags to HTML with unclosed tags", () => {
const value = "{{#strong}}Hello";
expect(customMarkupPlugin.process(value, "my_key", {}, null)).toBe(
"{{#strong}}Hello"
);
});

it("should not convert interpolation tags to HTML with different opening an closing tags", () => {
const value = "{{#b}}Hello{{/strong}}";
expect(customMarkupPlugin.process(value, "my_key", {}, null)).toBe(
"{{#b}}Hello{{/strong}}"
);
});
});
20 changes: 20 additions & 0 deletions src/modules/shared/i18n/customMarkupPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { PostProcessorModule } from "i18next";

const RANGE_REGEX = /{{#(\w+)}}(.*?){{\/\1}}/g;
const SELF_CLOSING_REGEX = /{{#(\w+)\/}}/g;

/**
* Custom i18next post processor to HTML interpolation tags to HTML.
*
* For example, `{{#strong}}Hello{{/strong}}` will be converted to `<strong>Hello</strong>`,
* and `{{#br/}}` will be converted to `<br />`.
*/
export const customMarkupPlugin: PostProcessorModule = {
type: "postProcessor",
name: "customMarkup",
process(value: string) {
return value
.replace(RANGE_REGEX, "<$1>$2</$1>")
.replace(SELF_CLOSING_REGEX, "<$1 />");
},
};
28 changes: 16 additions & 12 deletions src/modules/shared/i18n/initI18next.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import { customMarkupPlugin } from "./customMarkupPlugin";

export function initI18next(locale: string) {
i18next.use(initReactI18next).init({
resources: {
[`${locale}`]: {},
},
lng: locale,
lowerCaseLng: true,
interpolation: {
escapeValue: false,
},
keySeparator: false,
pluralSeparator: ".",
});
i18next
.use(initReactI18next)
.use(customMarkupPlugin)
.init({
resources: {
[`${locale}`]: {},
},
lng: locale,
lowerCaseLng: true,
interpolation: {
escapeValue: false,
},
keySeparator: false,
pluralSeparator: ".",
});
}

0 comments on commit 2619efa

Please sign in to comment.