` component is an alternative to the `i18n` pipe that supports template wrapping. It
+supports wrapping developer defined tags around parts the translated text. It allows translators to
+translate the original text more accurately and so that it still follows the grammar rules of the
+target language.
+
+The templating syntax uses numeric marker tags `<0>0>` around text that will be wrapped. The
+marker tags should numbered sequentially starting from 0. The marker tags are then matched and
+replaced by the `*bit-i18n-tag` directives in the order they appear in the template.
+
+If a corresponding `*bit-i18n-tag` directive is not found for a marker tag, the marker tag's content
+will be rendered as is.
+
+## Basic Example
+
+
+
+
+
+### Source
+
+```html
+
+
+ {{ text }}
+
+ {{ text }}
+
+
+
+ {{ text }}
+
+
+```
+
+## Attribute Selector Example
+
+You can also use the `bit-i18n` as an attribute to avoid creating an extra element in the DOM.
+
+
+
+
+
+### Source
+
+```html
+
+
+ {{ text }}
+ {{ text }}
+
+```
+
+## Missing Template Example
+
+If there are not enough `*bit-i18n-tag` directives to match the marker tags, the marker tags will be
+rendered as is.
+
+
+
+
+
+### Source
+
+```html
+
+
+ {{ text }}
+
+ {{ text }}
+
+
+
+```
diff --git a/libs/components/src/i18n/i18n.module.ts b/libs/components/src/i18n/i18n.module.ts
new file mode 100644
index 00000000000..795475c4fa7
--- /dev/null
+++ b/libs/components/src/i18n/i18n.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from "@angular/core";
+
+import { SharedModule } from "../shared";
+
+import { I18nTagDirective } from "./i18n-tag.directive";
+import { I18nComponent } from "./i18n.component";
+
+@NgModule({
+ imports: [SharedModule],
+ declarations: [I18nComponent, I18nTagDirective],
+ exports: [I18nComponent, I18nTagDirective],
+})
+export class I18nModule {}
diff --git a/libs/components/src/i18n/i18n.stories.ts b/libs/components/src/i18n/i18n.stories.ts
new file mode 100644
index 00000000000..f98abcd8da9
--- /dev/null
+++ b/libs/components/src/i18n/i18n.stories.ts
@@ -0,0 +1,76 @@
+import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
+
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+
+import { I18nMockService } from "../utils/i18n-mock.service";
+
+import { I18nComponent } from "./i18n.component";
+import { I18nModule } from "./i18n.module";
+
+export default {
+ title: "Component Library/I18n Templates",
+ component: I18nComponent,
+ decorators: [
+ moduleMetadata({
+ imports: [I18nModule],
+ providers: [
+ {
+ provide: I18nService,
+ useFactory: () => {
+ return new I18nMockService({
+ basicExample: `
+ This is an example with <0>link0> tags and <1>bold1> tags. The entire sentence
+ can be <2>translated as a whole2> and re-arranged according to each language's grammar rules.`,
+ otherExample: `
+ This is another example with <1>bold1> tags to show that tag order does not matter
+ and the <0>link0> tags are after.`,
+ });
+ },
+ },
+ ],
+ }),
+ ],
+ args: {},
+ parameters: {},
+} as Meta;
+
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: (args) => ({
+ props: args,
+ template: `
+
+ {{ text }}
+ {{ text }}
+
+ {{ text }}
+
+
+ `,
+ }),
+};
+
+export const AttributeSelector: Story = {
+ render: (args) => ({
+ props: args,
+ template: `
+
+ {{ text }}
+ {{ text }}
+
+ `,
+ }),
+};
+
+export const MissingTemplate: Story = {
+ render: (args) => ({
+ props: args,
+ template: `
+
+ {{ text }}
+ {{ text }}
+
+ `,
+ }),
+};
diff --git a/libs/components/src/i18n/index.ts b/libs/components/src/i18n/index.ts
new file mode 100644
index 00000000000..5bf65f88154
--- /dev/null
+++ b/libs/components/src/i18n/index.ts
@@ -0,0 +1 @@
+export * from "./i18n.module";
diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts
index d4fdda08a2a..337959849bd 100644
--- a/libs/components/src/index.ts
+++ b/libs/components/src/index.ts
@@ -10,6 +10,7 @@ export * from "./checkbox";
export * from "./color-password";
export * from "./dialog";
export * from "./form-field";
+export * from "./i18n";
export * from "./icon-button";
export * from "./icon";
export * from "./input";