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:
parent
8ccd36b0a7
commit
99ed08656a
14
.github/workflows/frontend-build-check.yaml
vendored
14
.github/workflows/frontend-build-check.yaml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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": {
|
||||
|
9
frontend/src/__generated__/locale-codes.ts
generated
9
frontend/src/__generated__/locale-codes.ts
generated
@ -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;
|
||||
|
0
frontend/src/__generated__/locales/.keep
generated
Normal file
0
frontend/src/__generated__/locales/.keep
generated
Normal file
1052
frontend/src/__generated__/locales/en-US.ts
generated
1052
frontend/src/__generated__/locales/en-US.ts
generated
File diff suppressed because it is too large
Load Diff
@ -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>`,
|
||||
)
|
||||
|
@ -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>
|
||||
|
@ -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)}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>`}
|
||||
|
@ -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
|
||||
|
@ -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"),
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
@ -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"), () =>
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user