mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 22:13:32 +00:00
Re-work I18nComponent to use Signals
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Directive, TemplateRef } from "@angular/core";
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
/**
|
||||
* Structural directive that can be used to mark a template reference inside an I18nComponent.
|
||||
@@ -8,8 +8,5 @@ import { Directive, TemplateRef } from "@angular/core";
|
||||
*/
|
||||
@Directive({
|
||||
selector: "[bit-i18n-part]",
|
||||
standalone: true,
|
||||
})
|
||||
export class I18nPartDirective {
|
||||
constructor(public templateRef: TemplateRef<any>) {}
|
||||
}
|
||||
export class I18nPartDirective {}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
AfterContentInit,
|
||||
Component,
|
||||
ContentChildren,
|
||||
Input,
|
||||
QueryList,
|
||||
TemplateRef,
|
||||
} from "@angular/core";
|
||||
import { Component, TemplateRef, input, computed, contentChildren } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -48,7 +41,7 @@ interface I18nStringPart {
|
||||
selector: "[bit-i18n]",
|
||||
imports: [SharedModule],
|
||||
template: `
|
||||
<ng-container *ngFor="let part of translationParts">
|
||||
<ng-container *ngFor="let part of translationParts()">
|
||||
<ng-container *ngIf="part.templateRef != undefined; else text">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="part.templateRef; context: { $implicit: part.text }"
|
||||
@@ -57,54 +50,47 @@ interface I18nStringPart {
|
||||
<ng-template #text>{{ part.text }}</ng-template>
|
||||
</ng-container>
|
||||
`,
|
||||
standalone: true,
|
||||
})
|
||||
export class I18nComponent implements AfterContentInit {
|
||||
@Input("bit-i18n")
|
||||
translationKey: string;
|
||||
export class I18nComponent {
|
||||
translationKey = input.required<string>({ alias: "bit-i18n" });
|
||||
|
||||
/**
|
||||
* Optional arguments to pass to the translation function.
|
||||
*/
|
||||
@Input()
|
||||
args: (string | number)[] = [];
|
||||
args = input<(string | number)[]>([]);
|
||||
|
||||
@ContentChildren(I18nPartDirective)
|
||||
templateTags: QueryList<I18nPartDirective>;
|
||||
private tagTemplates = contentChildren(I18nPartDirective, { read: TemplateRef });
|
||||
|
||||
protected translationParts: I18nStringPart[] = [];
|
||||
private translatedText = computed(() => {
|
||||
const translatedText = this.i18nService.t(this.translationKey(), ...this.args());
|
||||
return this.parseTranslatedString(translatedText);
|
||||
});
|
||||
|
||||
protected translationParts = computed<I18nStringPart[]>(() => {
|
||||
const [translationParts, tagCount] = this.translatedText();
|
||||
const tagTemplates = this.tagTemplates();
|
||||
const tagTemplateCount = tagTemplates.length;
|
||||
|
||||
if (tagCount !== tagTemplateCount) {
|
||||
this.logService.warning(
|
||||
`The translation for "${this.translationKey()}" has ${tagCount} template tag(s), but ${tagTemplateCount} bit-i18n-part directive(s) were found.`,
|
||||
);
|
||||
}
|
||||
|
||||
translationParts
|
||||
.filter((part) => part.tagId !== undefined)
|
||||
.forEach((part) => {
|
||||
part.templateRef = tagTemplates[part.tagId!];
|
||||
});
|
||||
|
||||
return translationParts;
|
||||
});
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
ngAfterContentInit() {
|
||||
const translatedText = this.i18nService.t(
|
||||
this.translationKey,
|
||||
this.args[0],
|
||||
this.args[1],
|
||||
this.args[2],
|
||||
);
|
||||
const [translationParts, tagCount] = this.parseTranslatedString(translatedText);
|
||||
this.translationParts = translationParts;
|
||||
|
||||
if (tagCount !== this.templateTags.length) {
|
||||
this.logService.warning(
|
||||
`The translation for "${this.translationKey}" has ${tagCount} template tag(s), but ${this.templateTags.length} bit-i18n-part directive(s) were found.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Assign any templateRefs to the translation parts
|
||||
this.templateTags.forEach((tag, index) => {
|
||||
this.translationParts.forEach((part) => {
|
||||
if (part.tagId === index) {
|
||||
part.templateRef = tag.templateRef;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a translated string into an array of parts separated by tag identifiers.
|
||||
* Tag identifiers must be numbers surrounded by angle brackets.
|
||||
@@ -118,7 +104,7 @@ export class I18nComponent implements AfterContentInit {
|
||||
private parseTranslatedString(inputString: string): [I18nStringPart[], number] {
|
||||
const regex = /<(\d+)>(.*?)<\/\1>|([^<]+)/g;
|
||||
const parts: I18nStringPart[] = [];
|
||||
let match: RegExpMatchArray;
|
||||
let match: RegExpMatchArray | null;
|
||||
let tagCount = 0;
|
||||
|
||||
while ((match = regex.exec(inputString)) !== null) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Canvas, Primary, Controls, Source } from "@storybook/addon-docs";
|
||||
import { Meta, Story } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./i18n.stories";
|
||||
|
||||
@@ -8,11 +8,10 @@ import * as stories from "./i18n.stories";
|
||||
|
||||
The `[bit-i18n]` 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.
|
||||
translate the original text more accurately to follow 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
|
||||
marker tags should be numbered sequentially starting from 0. The marker tags are then matched and
|
||||
replaced by the `*bit-i18n-part` directives in the order they appear in the template.
|
||||
|
||||
If a corresponding `*bit-i18n-part` directive is not found for a marker tag, the marker tag's
|
||||
@@ -68,7 +67,7 @@ be rendered as is.
|
||||
</div>
|
||||
```
|
||||
|
||||
## Missing Template Example
|
||||
## I18n Arguments Example
|
||||
|
||||
You can also pass arguments to the `i18nService.t()` method via the `[args]` input attribute;
|
||||
|
||||
@@ -80,7 +79,7 @@ You can also pass arguments to the `i18nService.t()` method via the `[args]` inp
|
||||
|
||||
```html
|
||||
<!-- argExample in messages.json
|
||||
`This is an example with <0>link</0> tags and $SOME_ARG$.`
|
||||
"This is an example with <0>link</0> tags and $SOME_ARG$."
|
||||
-->
|
||||
<div bit-i18n="argExample" [args]="['passed args']">
|
||||
<!-- <0></0> -->
|
||||
|
||||
Reference in New Issue
Block a user