feat: Localization workflow improvements (#2069)

- Extracts translatable text strings in pre-commit hook
- Updates ternary pluralization to use `pluralOf` instead
- Generates XLIFF for Spanish
This commit is contained in:
sua yoo 2024-09-10 14:15:26 -07:00 committed by GitHub
parent 8ccd36b0a7
commit 99ed08656a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 3113 additions and 3412 deletions

View File

@ -1,5 +1,14 @@
name: Frontend Build Check
on:
push:
branches:
- main
paths:
- 'frontend/src/**'
- 'frontend/*.json'
- 'frontend/*.js'
- 'frontend/*.ts'
- '.github/workflows/frontend-build-check.yaml'
pull_request:
paths:
- 'frontend/src/**'
@ -42,9 +51,12 @@ jobs:
- name: Unit tests
working-directory: frontend
run: yarn test
- name: Check extracted strings
working-directory: frontend
run: yarn localize:extract && git diff-index HEAD --
- name: Localization build
working-directory: frontend
run: yarn localize:prepare
run: yarn localize:build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

View File

@ -1,4 +1,13 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd frontend
npx lint-staged
# run hook only if frontend src changed
if git diff --name-only --cached | grep --quiet 'frontend/src/';
then
cd frontend
npx lint-staged
yarn localize:extract
git add xliff
else
echo "(no frontend/src changes - skipping pre-commit hook)"
fi

View File

@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
"sourceLocale": "en",
"targetLocales": ["en-US"],
"targetLocales": ["es"],
"tsConfig": "tsconfig.json",
"output": {
"mode": "runtime",

View File

@ -95,7 +95,6 @@
"lint:lit-analyzer": "lit-analyzer",
"format": "prettier --write .",
"format:check": "prettier --check .",
"localize:prepare": "yarn localize:extract && yarn localize:build",
"localize:extract": "lit-localize extract",
"localize:build": "lit-localize build"
},
@ -123,11 +122,7 @@
"chromium": "^3.0.3"
},
"lint-staged": {
"*.{ts,js}": [
"prettier --write",
"eslint --fix --quiet"
],
"*.{html,css,json,webmanifest}": "prettier --write"
"*.{ts,js,html,css,json,webmanifest}": "prettier --write"
},
"husky": {
"hooks": {

View File

@ -10,14 +10,9 @@ export const sourceLocale = `en`;
* The other locale codes that this application is localized into. Sorted
* lexicographically.
*/
export const targetLocales = [
`en-US`,
] as const;
export const targetLocales = [] as const;
/**
* All valid project locale codes. Sorted lexicographically.
*/
export const allLocales = [
`en`,
`en-US`,
] as const;
export const allLocales = [`en`] as const;

View File

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,8 @@ import { getAppSettings } from "@/utils/app";
import { DEPTH_SUPPORTED_SCOPES } from "@/utils/crawler";
import { humanizeSchedule } from "@/utils/cron";
import LiteElement, { html } from "@/utils/LiteElement";
import { formatNumber } from "@/utils/localization";
import { pluralOf } from "@/utils/pluralize";
/**
* Usage:
@ -146,13 +148,13 @@ export class ConfigDetails extends LiteElement {
msg("Max Pages"),
when(
maxPages,
() => msg(str`${maxPages!.toLocaleString()} pages`),
(val: number | string) =>
`${formatNumber(+val)} ${pluralOf("pages", +val)}`,
() =>
this.orgDefaults?.maxPagesPerCrawl
? html`<span class="text-neutral-400"
>${msg(
str`${this.orgDefaults.maxPagesPerCrawl.toLocaleString()} pages`,
)}
? html`<span class="text-neutral-400">
${formatNumber(this.orgDefaults.maxPagesPerCrawl)}
${pluralOf("pages", this.orgDefaults.maxPagesPerCrawl)}
${msg("(default)")}</span
>`
: undefined,
@ -316,7 +318,8 @@ export class ConfigDetails extends LiteElement {
html`<sl-tag class="mr-2 mt-1" variant="neutral">
${coll.name}
<span class="font-monostyle pl-1 text-xs">
(${msg(str`${coll.crawlCount} items`)})
(${formatNumber(coll.crawlCount)}
${pluralOf("items", coll.crawlCount)})
</span>
</sl-tag>`,
)

View File

@ -11,7 +11,7 @@
* </btrix-crawl-list>
* ```
*/
import { localized, msg, str } from "@lit/localize";
import { localized, msg } from "@lit/localize";
import { css, html, nothing, type TemplateResult } from "lit";
import {
customElement,
@ -27,7 +27,11 @@ import { RelativeDuration } from "@/components/ui/relative-duration";
import { NavigateController } from "@/controllers/navigate";
import type { Crawl } from "@/types/crawler";
import { renderName } from "@/utils/crawler";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { pluralOf } from "@/utils/pluralize";
const formatNumberCompact = (v: number) =>
formatNumber(v, { notation: "compact" });
/**
* @slot menu
@ -66,10 +70,6 @@ export class CrawlListItem extends TailwindElement {
@query("btrix-overflow-dropdown")
dropdownMenu!: OverflowDropdown;
private readonly numberFormatter = new Intl.NumberFormat(getLocale(), {
notation: "compact",
});
private readonly navigate = new NavigateController(this);
render() {
@ -121,6 +121,7 @@ export class CrawlListItem extends TailwindElement {
</btrix-table-cell>
`;
}
return html`
<btrix-table-row
class=${this.href
@ -204,21 +205,10 @@ export class CrawlListItem extends TailwindElement {
const pagesComplete = +(crawl.stats?.done || 0);
const pagesFound = +(crawl.stats?.found || 0);
if (crawl.finished) {
return pagesComplete === 1
? msg(str`${this.numberFormatter.format(pagesComplete)} page`)
: msg(str`${this.numberFormatter.format(pagesComplete)} pages`);
return `${formatNumberCompact(pagesComplete)} ${pluralOf("pages", pagesComplete)}`;
}
return pagesFound === 1
? msg(
str`${this.numberFormatter.format(
pagesComplete,
)} / ${this.numberFormatter.format(pagesFound)} page`,
)
: msg(
str`${this.numberFormatter.format(
pagesComplete,
)} / ${this.numberFormatter.format(pagesFound)} pages`,
);
return `${formatNumberCompact(pagesComplete)} / ${formatNumberCompact(pagesFound)} ${pluralOf("pages", pagesFound)}`;
})}
</btrix-table-cell>
<btrix-table-cell>

View File

@ -35,6 +35,8 @@ import type {
import type { ArchivedItem, Crawl, Upload, Workflow } from "@/types/crawler";
import { isApiError } from "@/utils/api";
import { finishedCrawlStates } from "@/utils/crawler";
import { formatNumber } from "@/utils/localization";
import { pluralOf } from "@/utils/pluralize";
const TABS = ["crawl", "upload"] as const;
type Tab = (typeof TABS)[number];
@ -555,16 +557,14 @@ export class CollectionItemsDialog extends BtrixElement {
const messages = [];
if (addCount) {
messages.push(
addCount === 1
? msg(str`Adding 1 item`)
: msg(str`Adding ${addCount.toLocaleString()} items`),
msg(
str`Adding ${formatNumber(addCount)} ${pluralOf("items", addCount)}`,
),
);
}
if (removeCount) {
messages.push(
removeCount === 1
? msg(str`Removing 1 item`)
: msg(str`Removing ${removeCount.toLocaleString()} items`),
str`Adding ${formatNumber(removeCount)} ${pluralOf("items", removeCount)}`,
);
}

View File

@ -15,7 +15,8 @@ import type {
} from "@/types/api";
import type { Crawl, Workflow } from "@/types/crawler";
import { finishedCrawlStates } from "@/utils/crawler";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { pluralOf } from "@/utils/pluralize";
export type SelectionChangeDetail = {
selection: Record<string, boolean>;
@ -358,15 +359,10 @@ export class CollectionWorkflowList extends BtrixElement {
const remainder = workflow.seedCount - 1;
let nameSuffix: string | TemplateResult<1> = "";
if (remainder) {
if (remainder === 1) {
nameSuffix = html`<span class="ml-1"
>${msg(str`+${remainder} URL`)}</span
>`;
} else {
nameSuffix = html`<span class="ml-1"
>${msg(str`+${remainder} URLs`)}</span
>`;
}
nameSuffix = html`<span class="ml-1"
>+${formatNumber(remainder, { notation: "compact" })}
${pluralOf("URLs", remainder)}</span
>`;
}
return html`
<div class="flex overflow-hidden whitespace-nowrap">

View File

@ -62,8 +62,9 @@ import {
humanizeSchedule,
} from "@/utils/cron";
import { maxLengthValidator } from "@/utils/form";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { isArchivingDisabled } from "@/utils/orgs";
import { pluralOf } from "@/utils/pluralize";
import { regexEscape } from "@/utils/string";
import { tw } from "@/utils/tailwind";
import {
@ -1725,11 +1726,9 @@ https://archiveweb.page/images/${"logo.svg"}`}
const firstUrl = urlList[0].trim();
if (urlList.length > 1) {
const remainder = urlList.length - 1;
if (remainder === 1) {
jobName = msg(str`${firstUrl} + ${remainder} more URL`);
} else {
jobName = msg(str`${firstUrl} + ${remainder} more URLs`);
}
jobName = msg(
str`${firstUrl} + ${formatNumber(remainder, { notation: "compact" })} more ${pluralOf("URLs", remainder)}`,
);
} else {
jobName = firstUrl;
}

View File

@ -26,8 +26,12 @@ import { NavigateController } from "@/controllers/navigate";
import type { ListWorkflow } from "@/types/crawler";
import { humanizeSchedule } from "@/utils/cron";
import { srOnly, truncate } from "@/utils/css";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { numberFormatter } from "@/utils/number";
import { pluralOf } from "@/utils/pluralize";
const formatNumberCompact = (v: number) =>
formatNumber(v, { notation: "compact" });
// postcss-lit-disable-next-line
const mediumBreakpointCss = css`30rem`;
@ -213,10 +217,6 @@ export class WorkflowListItem extends LitElement {
private readonly navigate = new NavigateController(this);
private readonly numberFormatter = numberFormatter(getLocale(), {
notation: "compact",
});
render() {
const notSpecified = html`<span class="notSpecified" role="presentation"
>---</span
@ -353,14 +353,9 @@ export class WorkflowListItem extends LitElement {
})}
</div>
<div class="desc">
${this.safeRender((workflow) =>
workflow.crawlCount === 1
? msg(str`${workflow.crawlCount} crawl`)
: msg(
str`${this.numberFormatter.format(
workflow.crawlCount || 0,
)} crawls`,
),
${this.safeRender(
(workflow) =>
`${formatNumberCompact(workflow.crawlCount)} ${pluralOf("crawls", workflow.crawlCount)}`,
)}
</div>
</div>
@ -420,15 +415,10 @@ export class WorkflowListItem extends LitElement {
const remainder = workflow.seedCount - 1;
let nameSuffix: string | TemplateResult<1> = "";
if (remainder) {
if (remainder === 1) {
nameSuffix = html`<span class="additionalUrls"
>${msg(str`+${remainder} URL`)}</span
>`;
} else {
nameSuffix = html`<span class="additionalUrls"
>${msg(str`+${remainder} URLs`)}</span
>`;
}
nameSuffix = html`<span class="additionalUrls"
>+${formatNumber(remainder, { notation: "compact" })}
${pluralOf("URLs", remainder)}</span
>`;
}
return html`
<span class="primaryUrl truncate">${workflow.firstSeed}</span

View File

@ -31,8 +31,9 @@ import {
renderName,
} from "@/utils/crawler";
import { humanizeExecutionSeconds } from "@/utils/executionTimeFormatter";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { isArchivingDisabled } from "@/utils/orgs";
import { pluralOf } from "@/utils/pluralize";
import { tw } from "@/utils/tailwind";
import "./ui/qa";
@ -142,8 +143,6 @@ export class ArchivedItemDetail extends BtrixElement {
return `${this.navigate.orgBasePath}/${path}`;
}
private readonly numberFormatter = new Intl.NumberFormat(getLocale());
private timerId?: number;
private get isActive(): boolean | null {
@ -864,15 +863,13 @@ export class ArchivedItemDetail extends BtrixElement {
? " text-purple-600"
: ""} font-mono"
>
${this.numberFormatter.format(
+this.crawl.stats.done,
)}
${formatNumber(+this.crawl.stats.done)}
<span class="text-0-400">/</span>
${this.numberFormatter.format(
+this.crawl.stats.found,
)}
${formatNumber(+this.crawl.stats.found)}
</span>
<span>${msg("pages")}</span>`
<span
>${pluralOf("pages", +this.crawl.stats.found)}</span
>`
: ""}`
: html`<span class="text-0-400">${msg("Unknown")}</span>`}`
: html`<sl-skeleton class="h-[16px] w-24"></sl-skeleton>`}

View File

@ -16,6 +16,7 @@ import { isApiError } from "@/utils/api";
import { maxLengthValidator } from "@/utils/form";
import { formatNumber, getLocale } from "@/utils/localization";
import { isArchivingDisabled } from "@/utils/orgs";
import { pluralOf } from "@/utils/pluralize";
const DESCRIPTION_MAXLENGTH = 500;
@ -360,15 +361,10 @@ export class BrowserProfilesDetail extends BtrixElement {
const remainder = workflow.seedCount - 1;
let nameSuffix: string | TemplateResult<1> = "";
if (remainder) {
if (remainder === 1) {
nameSuffix = html`<span class="ml-2 text-neutral-500"
>${msg(str`+${remainder} URL`)}</span
>`;
} else {
nameSuffix = html`<span class="ml-2 text-neutral-500"
>${msg(str`+${remainder} URLs`)}</span
>`;
}
nameSuffix = html`<span class="ml-2 text-neutral-500"
>+${formatNumber(remainder, { notation: "compact" })}
${pluralOf("URLs", remainder)}</span
>`;
}
return html`
<span class="primaryUrl truncate">${workflow.firstSeed}</span

View File

@ -18,7 +18,8 @@ import type {
} from "@/types/api";
import type { Collection } from "@/types/collection";
import type { ArchivedItem, Crawl, CrawlState, Upload } from "@/types/crawler";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { pluralOf } from "@/utils/pluralize";
const ABORT_REASON_THROTTLE = "throttled";
const DESCRIPTION_MAX_HEIGHT_PX = 200;
@ -62,10 +63,6 @@ export class CollectionDetail extends BtrixElement {
// Use to cancel requests
private getArchivedItemsController: AbortController | null = null;
private readonly numberFormatter = new Intl.NumberFormat(getLocale(), {
notation: "compact",
});
private readonly tabLabels: Record<
Tab,
{ icon: { name: string; library: string }; text: string }
@ -481,10 +478,10 @@ export class CollectionDetail extends BtrixElement {
private renderInfoBar() {
return html`
<btrix-desc-list horizontal>
${this.renderDetailItem(msg("Archived Items"), (col) =>
col.crawlCount === 1
? msg("1 item")
: msg(str`${this.numberFormatter.format(col.crawlCount)} items`),
${this.renderDetailItem(
msg("Archived Items"),
(col) =>
`${formatNumber(col.crawlCount)} ${pluralOf("items", col.crawlCount)}`,
)}
${this.renderDetailItem(
msg("Total Size"),
@ -494,10 +491,10 @@ export class CollectionDetail extends BtrixElement {
display="narrow"
></sl-format-bytes>`,
)}
${this.renderDetailItem(msg("Total Pages"), (col) =>
col.pageCount === 1
? msg("1 page")
: msg(str`${this.numberFormatter.format(col.pageCount)} pages`),
${this.renderDetailItem(
msg("Total Pages"),
(col) =>
`${formatNumber(col.pageCount)} ${pluralOf("pages", col.pageCount)}`,
)}
${this.renderDetailItem(
msg("Last Updated"),

View File

@ -1,4 +1,4 @@
import { localized, msg, str } from "@lit/localize";
import { localized, msg } from "@lit/localize";
import type { SlInput, SlMenuItem } from "@shoelace-style/shoelace";
import Fuse from "fuse.js";
import { html, type PropertyValues } from "lit";
@ -18,10 +18,14 @@ import type { APIPaginatedList, APIPaginationQuery } from "@/types/api";
import type { Collection, CollectionSearchValues } from "@/types/collection";
import type { UnderlyingFunction } from "@/types/utils";
import { isApiError } from "@/utils/api";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { pluralOf } from "@/utils/pluralize";
import { tw } from "@/utils/tailwind";
import noCollectionsImg from "~assets/images/no-collections-found.webp";
const formatNumberCompact = (v: number) =>
formatNumber(v, { notation: "compact" });
type Collections = APIPaginatedList<Collection>;
type SearchFields = "name";
type SearchResult = {
@ -102,10 +106,6 @@ export class CollectionsList extends BtrixElement {
return this.searchByValue.length >= MIN_SEARCH_LENGTH;
}
private readonly numberFormatter = new Intl.NumberFormat(getLocale(), {
notation: "compact",
});
protected async willUpdate(
changedProperties: PropertyValues<this> & Map<string, unknown>,
) {
@ -526,9 +526,7 @@ export class CollectionsList extends BtrixElement {
</a>
</btrix-table-cell>
<btrix-table-cell>
${col.crawlCount === 1
? msg("1 item")
: msg(str`${this.numberFormatter.format(col.crawlCount)} items`)}
${formatNumber(col.crawlCount)} ${pluralOf("items", col.crawlCount)}
</btrix-table-cell>
<btrix-table-cell>
<sl-format-bytes
@ -537,9 +535,8 @@ export class CollectionsList extends BtrixElement {
></sl-format-bytes>
</btrix-table-cell>
<btrix-table-cell>
${col.pageCount === 1
? msg("1 page")
: msg(str`${this.numberFormatter.format(col.pageCount)} pages`)}
${formatNumberCompact(col.pageCount)}
${pluralOf("pages", col.pageCount)}
</btrix-table-cell>
<btrix-table-cell>
<sl-format-date

View File

@ -275,35 +275,48 @@ export class OrgSettingsBilling extends BtrixElement {
: nothing}`;
};
private readonly renderQuotas = (quotas: OrgQuotas) => html`
<ul class="leading-relaxed text-neutral-700">
<li>
${msg(
str`${quotas.maxExecMinutesPerMonth ? humanizeSeconds(quotas.maxExecMinutesPerMonth * 60, undefined, undefined, "long") : msg("Unlimited minutes")} of crawl and QA analysis execution time`,
)}
</li>
<li>
${msg(
html`${quotas.storageQuota
? html`<sl-format-bytes
value=${quotas.storageQuota}
></sl-format-bytes>`
: msg("Unlimited")}
storage`,
)}
</li>
<li>
${msg(
str`${quotas.maxPagesPerCrawl ? formatNumber(quotas.maxPagesPerCrawl) : msg("Unlimited")} ${pluralOf("pages", quotas.maxPagesPerCrawl)} per crawl`,
)}
</li>
<li>
${msg(
str`${quotas.maxConcurrentCrawls ? formatNumber(quotas.maxConcurrentCrawls) : msg("Unlimited")} concurrent ${pluralOf("crawls", quotas.maxConcurrentCrawls)}`,
)}
</li>
</ul>
`;
private readonly renderQuotas = (quotas: OrgQuotas) => {
const maxExecMinutesPerMonth =
quotas.maxExecMinutesPerMonth &&
humanizeSeconds(
quotas.maxExecMinutesPerMonth * 60,
undefined,
undefined,
"long",
);
const maxPagesPerCrawl =
quotas.maxPagesPerCrawl &&
`${formatNumber(quotas.maxPagesPerCrawl)} ${pluralOf("pages", quotas.maxPagesPerCrawl)}`;
const maxConcurrentCrawls =
quotas.maxConcurrentCrawls &&
msg(
str`${formatNumber(quotas.maxConcurrentCrawls)} concurrent ${pluralOf("crawls", quotas.maxConcurrentCrawls)}`,
);
return html`
<ul class="leading-relaxed text-neutral-700">
<li>
${msg(
str`${maxExecMinutesPerMonth || msg("Unlimited minutes")} of crawl and QA analysis execution time`,
)}
</li>
<li>
${msg(
html`${quotas.storageQuota
? html`<sl-format-bytes
value=${quotas.storageQuota}
></sl-format-bytes>`
: msg("Unlimited")}
storage`,
)}
</li>
<li>
${msg(str`${maxPagesPerCrawl || msg("Unlimited pages")} per crawl`)}
</li>
<li>${maxConcurrentCrawls || msg("Unlimited concurrent crawls")}</li>
</ul>
`;
};
private renderPortalLink() {
return html`

View File

@ -32,7 +32,7 @@ import {
} from "@/utils/crawler";
import { humanizeSchedule } from "@/utils/cron";
import LiteElement, { html } from "@/utils/LiteElement";
import { getLocale } from "@/utils/localization";
import { formatNumber, getLocale } from "@/utils/localization";
import { isArchivingDisabled } from "@/utils/orgs";
const SECTIONS = ["crawls", "watch", "settings", "logs"] as const;
@ -110,9 +110,6 @@ export class WorkflowDetail extends LiteElement {
@state()
private filterBy: Partial<Record<keyof Crawl, string | CrawlState[]>> = {};
private readonly numberFormatter = new Intl.NumberFormat(getLocale(), {
// notation: "compact",
});
private readonly dateFormatter = new Intl.DateTimeFormat(getLocale(), {
year: "numeric",
month: "numeric",
@ -924,13 +921,9 @@ export class WorkflowDetail extends LiteElement {
<btrix-desc-list horizontal>
${this.renderDetailItem(msg("Pages Crawled"), () =>
this.lastCrawlStats
? msg(
str`${this.numberFormatter.format(
+(this.lastCrawlStats.done || 0),
)} / ${this.numberFormatter.format(
+(this.lastCrawlStats.found || 0),
)}`,
)
? `${formatNumber(
+(this.lastCrawlStats.done || 0),
)} / ${formatNumber(+(this.lastCrawlStats.found || 0))}`
: html`<sl-spinner></sl-spinner>`,
)}
${this.renderDetailItem(msg("Run Duration"), () =>

View File

@ -1,7 +1,10 @@
import { msg, str } from "@lit/localize";
import { msg } from "@lit/localize";
import clsx from "clsx";
import { html, type TemplateResult } from "lit";
import { formatNumber } from "./localization";
import { pluralOf } from "./pluralize";
import type { ArchivedItem, CrawlState, Workflow } from "@/types/crawler";
export const activeCrawlStates: CrawlState[] = [
@ -52,15 +55,10 @@ export function renderName(item: ArchivedItem | Workflow, className?: string) {
const remainder = item.seedCount - 1;
let nameSuffix: string | TemplateResult<1> = "";
if (remainder) {
if (remainder === 1) {
nameSuffix = html`<div class="ml-1">
${msg(str`+${remainder} URL`)}
</div>`;
} else {
nameSuffix = html`<div class="ml-1">
${msg(str`+${remainder} URLs`)}
</div>`;
}
nameSuffix = html`<div class="ml-1">
+${formatNumber(remainder, { notation: "compact" })}
${pluralOf("URLs", remainder)}
</div>`;
}
return html`
<div class="inline-flex w-full overflow-hidden whitespace-nowrap">

View File

@ -1,10 +1,7 @@
import { msg, str } from "@lit/localize";
import type { SlInput, SlTextarea } from "@shoelace-style/shoelace";
import { getLocale } from "./localization";
// TODO listen for localize changes and update
const numberFormatter = new Intl.NumberFormat(getLocale());
import { formatNumber } from "./localization";
export type MaxLengthValidator = {
helpText: string;
@ -12,15 +9,13 @@ export type MaxLengthValidator = {
};
export function getHelpText(maxLength: number, currentLength: number) {
const helpText = msg(
str`Maximum ${numberFormatter.format(maxLength)} characters`,
);
const helpText = msg(str`Maximum ${formatNumber(maxLength)} characters`);
if (currentLength > maxLength) {
const overMax = currentLength - maxLength;
return overMax === 1
? msg(str`${numberFormatter.format(overMax)} character over limit`)
: msg(str`${numberFormatter.format(overMax)} characters over limit`);
? msg(str`${formatNumber(overMax)} character over limit`)
: msg(str`${formatNumber(overMax)} characters over limit`);
}
return helpText;

View File

@ -30,6 +30,32 @@ const plurals = {
id: "crawls.plural.other",
}),
},
items: {
zero: msg("items", {
desc: 'plural form of "item" for zero items',
id: "items.plural.zero",
}),
one: msg("item", {
desc: 'singular form for "item"',
id: "items.plural.one",
}),
two: msg("items", {
desc: 'plural form of "item" for two items',
id: "items.plural.two",
}),
few: msg("items", {
desc: 'plural form of "item" for few items',
id: "items.plural.few",
}),
many: msg("items", {
desc: 'plural form of "item" for many items',
id: "items.plural.many",
}),
other: msg("items", {
desc: 'plural form of "item" for multiple/other items',
id: "items.plural.other",
}),
},
pages: {
zero: msg("pages", {
desc: 'plural form of "page" for zero pages',

File diff suppressed because it is too large Load Diff