From 54a44ea2394526b33093850f412d73096ea8d1bd Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 12 Nov 2024 14:11:31 -0800 Subject: [PATCH 1/6] prevent directly setting locale --- frontend/src/classes/BtrixElement.ts | 2 + frontend/src/components/ui/language-select.ts | 5 +- .../src/components/ui/user-language-select.ts | 26 +++---- frontend/src/controllers/localize.ts | 74 +++++++++++++++++++ frontend/src/index.ts | 24 ++---- frontend/src/pages/account-settings.ts | 13 ++-- frontend/src/types/localization.ts | 19 +++-- frontend/src/types/user.ts | 4 +- frontend/src/utils/localization.ts | 45 +++-------- frontend/src/utils/state.ts | 4 + 10 files changed, 135 insertions(+), 81 deletions(-) create mode 100644 frontend/src/controllers/localize.ts diff --git a/frontend/src/classes/BtrixElement.ts b/frontend/src/classes/BtrixElement.ts index 3f934dba9..8bfc76484 100644 --- a/frontend/src/classes/BtrixElement.ts +++ b/frontend/src/classes/BtrixElement.ts @@ -1,6 +1,7 @@ import { TailwindElement } from "./TailwindElement"; import { APIController } from "@/controllers/api"; +import { LocalizeController } from "@/controllers/localize"; import { NavigateController } from "@/controllers/navigate"; import { NotifyController } from "@/controllers/notify"; import appState, { use } from "@/utils/state"; @@ -13,6 +14,7 @@ export class BtrixElement extends TailwindElement { readonly api = new APIController(this); readonly notify = new NotifyController(this); readonly navigate = new NavigateController(this); + readonly localize = new LocalizeController(this); protected get authState() { return this.appState.auth; diff --git a/frontend/src/components/ui/language-select.ts b/frontend/src/components/ui/language-select.ts index e4a869208..eeb1ff72c 100644 --- a/frontend/src/components/ui/language-select.ts +++ b/frontend/src/components/ui/language-select.ts @@ -1,15 +1,16 @@ import { localized, msg } from "@lit/localize"; import type { SlSelect } from "@shoelace-style/shoelace"; -import ISO6391, { type LanguageCode } from "iso-639-1"; +import ISO6391 from "iso-639-1"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import sortBy from "lodash/fp/sortBy"; +import { allLanguageCodes, type LanguageCode } from "@/types/localization"; import { getLang } from "@/utils/localization"; const languages = sortBy("name")( - ISO6391.getLanguages(ISO6391.getAllCodes()), + ISO6391.getLanguages(allLanguageCodes), ) as unknown as { code: LanguageCode; name: string; diff --git a/frontend/src/components/ui/user-language-select.ts b/frontend/src/components/ui/user-language-select.ts index eba4547ad..1869230d0 100644 --- a/frontend/src/components/ui/user-language-select.ts +++ b/frontend/src/components/ui/user-language-select.ts @@ -4,9 +4,10 @@ import { customElement, state } from "lit/decorators.js"; import { sourceLocale } from "@/__generated__/locale-codes"; import { BtrixElement } from "@/classes/BtrixElement"; -import { allLocales, type LocaleCodeEnum } from "@/types/localization"; -import { getLocale, setLocale } from "@/utils/localization"; -import { AppStateService } from "@/utils/state"; +import { + translatedLocales, + type TranslatedLocaleEnum, +} from "@/types/localization"; /** * Select language that Browsertrix app will be shown in @@ -23,9 +24,7 @@ export class LocalePicker extends BtrixElement { private setLocaleNames() { const localeNames: LocalePicker["localeNames"] = {}; - // TODO Add browser-preferred languages - // https://github.com/webrecorder/browsertrix/issues/2143 - allLocales.forEach((locale) => { + this.localize.languages.forEach((locale) => { const name = new Intl.DisplayNames([locale], { type: "language", }).of(locale); @@ -39,8 +38,7 @@ export class LocalePicker extends BtrixElement { } render() { - const selectedLocale = - this.appState.userPreferences?.locale || sourceLocale; + const selectedLocale = this.appState.userLanguage || sourceLocale; return html` ${this.localeNames[selectedLocale as LocaleCodeEnum]}${this.localeNames[selectedLocale as TranslatedLocaleEnum]} @@ -80,12 +78,8 @@ export class LocalePicker extends BtrixElement { } async localeChanged(event: SlSelectEvent) { - const newLocale = event.detail.item.value as LocaleCodeEnum; + const newLocale = event.detail.item.value as TranslatedLocaleEnum; - AppStateService.partialUpdateUserPreferences({ locale: newLocale }); - - if (newLocale !== getLocale()) { - void setLocale(newLocale); - } + this.localize.setLanguage(newLocale); } } diff --git a/frontend/src/controllers/localize.ts b/frontend/src/controllers/localize.ts new file mode 100644 index 000000000..83d3a2ff0 --- /dev/null +++ b/frontend/src/controllers/localize.ts @@ -0,0 +1,74 @@ +import { configureLocalization } from "@lit/localize"; +import type { ReactiveController, ReactiveControllerHost } from "lit"; +import uniq from "lodash/fp/uniq"; + +import { sourceLocale, targetLocales } from "@/__generated__/locale-codes"; +import { + languageCodeSchema, + translatedLocales, + type AllLanguageCodes, + type LanguageCode, +} from "@/types/localization"; +import { getLang, langShortCode } from "@/utils/localization"; +import appState, { AppStateService } from "@/utils/state"; + +const { getLocale, setLocale } = configureLocalization({ + sourceLocale, + targetLocales, + loadLocale: async (locale: string) => + import(`/src/__generated__/locales/${locale}.ts`), +}); + +/** + * Manage app localization + */ +export class LocalizeController implements ReactiveController { + private readonly host: ReactiveControllerHost & EventTarget; + + private activeLocale: LanguageCode = sourceLocale; + + get languages() { + return uniq([ + ...translatedLocales, + ...window.navigator.languages.map(langShortCode), + ]); + } + + constructor(host: LocalizeController["host"]) { + this.host = host; + host.addController(this); + } + + hostConnected() {} + hostDisconnected() {} + + initLanguage() { + this.activeLocale = appState.userLanguage || getLang() || sourceLocale; + + this.setTranslation(this.activeLocale); + } + + /** + * User-initiated language setting + */ + setLanguage(lang: LanguageCode) { + const { error } = languageCodeSchema.safeParse(lang); + + if (error) { + console.debug("Error setting language:", error); + return; + } + + this.activeLocale = lang; + this.setTranslation(lang); + } + + private setTranslation(lang: LanguageCode) { + if ( + lang !== getLocale() && + (translatedLocales as AllLanguageCodes).includes(lang) + ) { + void setLocale(lang); + } + } +} diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 495303353..673cbb294 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -30,14 +30,12 @@ import type { NavigateEventDetail } from "@/controllers/navigate"; import type { NotifyEventDetail } from "@/controllers/notify"; import { theme } from "@/theme"; import { type Auth } from "@/types/auth"; -import { translatedLocales, type LocaleCodeEnum } from "@/types/localization"; -import { type AppSettings } from "@/utils/app"; import { - getLocale, - LOCALE_PARAM_NAME, - resetLocale, - setLocale, -} from "@/utils/localization"; + translatedLocales, + type TranslatedLocaleEnum, +} from "@/types/localization"; +import { type AppSettings } from "@/utils/app"; +import { LOCALE_PARAM_NAME } from "@/utils/localization"; import brandLockupColor from "~assets/brand/browsertrix-lockup-color.svg"; import "./shoelace"; @@ -157,12 +155,7 @@ export class App extends BtrixElement { } protected firstUpdated(): void { - if ( - this.appState.userPreferences?.locale && - this.appState.userPreferences.locale !== getLocale() - ) { - void setLocale(this.appState.userPreferences.locale); - } + this.localize.initLanguage(); } getLocationPathname() { @@ -894,7 +887,7 @@ export class App extends BtrixElement { } onSelectLocale(e: SlSelectEvent) { - const locale = e.detail.item.value as LocaleCodeEnum; + const locale = e.detail.item.value as TranslatedLocaleEnum; const url = new URL(window.location.href); url.searchParams.set(LOCALE_PARAM_NAME, locale); @@ -1021,8 +1014,7 @@ export class App extends BtrixElement { private clearUser() { this.authService.logout(); this.authService = new AuthService(); - AppStateService.resetAll(); - void resetLocale(); + AppStateService.resetUser(); } private showDialog(content: DialogContent) { diff --git a/frontend/src/pages/account-settings.ts b/frontend/src/pages/account-settings.ts index 14ce5fbf9..03a484ca4 100644 --- a/frontend/src/pages/account-settings.ts +++ b/frontend/src/pages/account-settings.ts @@ -11,7 +11,7 @@ import debounce from "lodash/fp/debounce"; import { TailwindElement } from "@/classes/TailwindElement"; import needLogin from "@/decorators/needLogin"; import { pageHeader } from "@/layouts/pageHeader"; -import { allLocales, type LocaleCodeEnum } from "@/types/localization"; +import { LanguageCode, translatedLocales } from "@/types/localization"; import type { UnderlyingFunction } from "@/types/utils"; import { isApiError } from "@/utils/api"; import LiteElement, { html } from "@/utils/LiteElement"; @@ -242,7 +242,7 @@ export class AccountSettings extends LiteElement { - ${(allLocales as unknown as string[]).length > 1 + ${(translatedLocales as unknown as string[]).length > 1 ? this.renderLanguage() : nothing} `; @@ -526,11 +526,14 @@ export class AccountSettings extends LiteElement { this.sectionSubmitting = null; } + /** + * Save language setting in local storage + */ private readonly onSelectLocale = async (e: SlSelectEvent) => { - const locale = e.detail.item.value as LocaleCodeEnum; + const locale = e.detail.item.value as LanguageCode; - if (locale !== this.appState.userPreferences?.locale) { - AppStateService.partialUpdateUserPreferences({ locale }); + if (locale !== this.appState.userPreferences?.language) { + AppStateService.partialUpdateUserPreferences({ language: locale }); } this.notify({ diff --git a/frontend/src/types/localization.ts b/frontend/src/types/localization.ts index 58c0e9c2c..565b87fc9 100644 --- a/frontend/src/types/localization.ts +++ b/frontend/src/types/localization.ts @@ -1,10 +1,19 @@ +import ISO6391, { type LanguageCode } from "iso-639-1"; import { z } from "zod"; import { allLocales } from "@/__generated__/locale-codes"; -export { allLocales }; -// Translated languages to show in app: -export const translatedLocales = ["en"] as const; +export { allLocales as translatedLocales }; -export const localeCodeEnum = z.enum(allLocales); -export type LocaleCodeEnum = z.infer; +export const translatedLocaleEnum = z.enum(allLocales); +export type TranslatedLocaleEnum = z.infer; + +export const allLanguageCodes = ISO6391.getAllCodes(); +export type AllLanguageCodes = readonly LanguageCode[]; + +export const languageCodeSchema = z.custom((val) => + typeof val === "string" + ? (allLanguageCodes as string[]).includes(val) + : false, +); +export type { LanguageCode }; diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts index 6ecfa4e82..05dfb1047 100644 --- a/frontend/src/types/user.ts +++ b/frontend/src/types/user.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { localeCodeEnum } from "./localization"; +import { languageCodeSchema } from "./localization"; import { accessCodeSchema } from "./org"; import { WorkflowScopeType } from "./workflow"; @@ -49,6 +49,6 @@ export type UserInfo = z.infer; export const userPreferencesSchema = z.object({ newWorkflowScopeType: z.nativeEnum(WorkflowScopeType).optional(), - locale: localeCodeEnum.optional(), + language: languageCodeSchema.optional(), }); export type UserPreferences = z.infer; diff --git a/frontend/src/utils/localization.ts b/frontend/src/utils/localization.ts index 165a5069d..08e373164 100644 --- a/frontend/src/utils/localization.ts +++ b/frontend/src/utils/localization.ts @@ -1,40 +1,11 @@ -import { configureLocalization } from "@lit/localize"; - -import { - allLocales, - sourceLocale, - targetLocales, -} from "@/__generated__/locale-codes"; -import { type LocaleCodeEnum } from "@/types/localization"; - -export const { getLocale, setLocale } = configureLocalization({ - sourceLocale, - targetLocales, - loadLocale: async (locale: string) => - import(`/src/__generated__/locales/${locale}.ts`), -}); +import { sourceLocale } from "@/__generated__/locale-codes"; +import type { LanguageCode } from "@/types/localization"; +import appState from "@/utils/state"; export const LOCALE_PARAM_NAME = "locale" as const; -export const getLocaleFromUrl = () => { - const url = new URL(window.location.href); - const locale = url.searchParams.get(LOCALE_PARAM_NAME); - - if (allLocales.includes(locale as unknown as LocaleCodeEnum)) { - return locale as LocaleCodeEnum; - } -}; - -export const setLocaleFromUrl = async () => { - const locale = getLocaleFromUrl(); - - if (!locale) return; - - await setLocale(locale); -}; - -export const resetLocale = async () => { - await setLocale(sourceLocale); +export const getLocale = () => { + return appState.userLanguage || getLang() || sourceLocale; }; /** @@ -80,11 +51,15 @@ export const formatISODateString = ( }, ); +export function langShortCode(locale: string) { + return locale.split("-")[0] as LanguageCode; +} + export function getLang() { // Default to current user browser language const browserLanguage = window.navigator.language; if (browserLanguage) { - return browserLanguage.slice(0, browserLanguage.indexOf("-")); + return langShortCode(browserLanguage); } return null; } diff --git a/frontend/src/utils/state.ts b/frontend/src/utils/state.ts index c963b12ae..0e16c6f49 100644 --- a/frontend/src/utils/state.ts +++ b/frontend/src/utils/state.ts @@ -85,6 +85,10 @@ export function makeAppStateService() { if (userOrg) return isCrawler(userOrg.role); return false; } + + get userLanguage() { + return this.userPreferences?.language; + } } const appState = new AppState(); From 1e1a3789b163fda5b437e0c7bad98d46426d1e87 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 12 Nov 2024 15:08:14 -0800 Subject: [PATCH 2/6] localize numbers --- frontend/src/components/orgs-list.ts | 4 +- frontend/src/controllers/localize.ts | 36 +++++- .../archived-items/archived-item-list.ts | 10 +- .../src/features/archived-items/crawl-list.ts | 10 +- .../src/features/archived-items/crawl-logs.ts | 6 +- .../crawl-pending-exclusions.ts | 2 +- .../features/archived-items/crawl-queue.ts | 20 ++-- .../select-browser-profile.ts | 19 ++-- .../collections/collection-items-dialog.ts | 10 +- .../collections/collection-workflow-list.ts | 12 +- .../crawl-workflows/workflow-editor.ts | 21 ++-- .../features/crawl-workflows/workflow-list.ts | 8 +- .../src/features/org/usage-history-table.ts | 3 +- .../src/features/qa/page-list/page-list.ts | 8 +- frontend/src/index.ts | 7 +- frontend/src/pages/account-settings.ts | 2 +- .../archived-item-detail.ts | 6 +- .../pages/org/archived-item-detail/ui/qa.ts | 12 +- .../org/archived-item-qa/archived-item-qa.ts | 6 +- .../org/archived-item-qa/ui/resources.ts | 17 +-- .../src/pages/org/browser-profiles-detail.ts | 6 +- .../src/pages/org/browser-profiles-list.ts | 7 +- frontend/src/pages/org/collection-detail.ts | 4 +- frontend/src/pages/org/collections-list.ts | 4 +- frontend/src/pages/org/dashboard.ts | 2 +- .../pages/org/settings/components/billing.ts | 6 +- frontend/src/pages/org/workflow-detail.ts | 105 +++++++++--------- frontend/src/pages/org/workflows-list.ts | 89 ++++++++------- frontend/src/utils/cron.ts | 21 ++-- frontend/src/utils/executionTimeFormatter.ts | 4 +- frontend/src/utils/localization.ts | 15 +-- frontend/src/utils/slugify.ts | 8 +- frontend/src/utils/workflow.ts | 3 +- 33 files changed, 270 insertions(+), 223 deletions(-) diff --git a/frontend/src/components/orgs-list.ts b/frontend/src/components/orgs-list.ts index faa421204..91654750d 100644 --- a/frontend/src/components/orgs-list.ts +++ b/frontend/src/components/orgs-list.ts @@ -14,7 +14,7 @@ import { when } from "lit/directives/when.js"; import { BtrixElement } from "@/classes/BtrixElement"; import type { Dialog } from "@/components/ui/dialog"; import type { ProxiesAPIResponse, Proxy } from "@/types/crawler"; -import { formatNumber, getLocale } from "@/utils/localization"; +import { formatNumber } from "@/utils/localization"; import type { OrgData } from "@/utils/orgs"; /** @@ -624,7 +624,7 @@ export class OrgsList extends BtrixElement { @@ -71,6 +72,7 @@ export class CrawlListItem extends TailwindElement { dropdownMenu!: OverflowDropdown; private readonly navigate = new NavigateController(this); + private readonly localize = new LocalizeController(this); render() { if (!this.crawl) return; @@ -82,7 +84,7 @@ export class CrawlListItem extends TailwindElement { ${this.safeRender( (crawl) => html` html` @@ -124,7 +126,7 @@ export class CrawlLogs extends LitElement {
${this.total ? this.total > 1 - ? msg(str`+${this.total.toLocaleString()} URLs`) + ? msg(str`+${this.total} URLs`) : msg(str`+1 URL`) : msg("No matches")} diff --git a/frontend/src/features/archived-items/crawl-queue.ts b/frontend/src/features/archived-items/crawl-queue.ts index c77ee306f..e7f692920 100644 --- a/frontend/src/features/archived-items/crawl-queue.ts +++ b/frontend/src/features/archived-items/crawl-queue.ts @@ -4,12 +4,12 @@ import type { SlInput, SlInputEvent, } from "@shoelace-style/shoelace"; -import type { PropertyValues } from "lit"; +import { html, type PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { when } from "lit/directives/when.js"; import throttle from "lodash/fp/throttle"; -import LiteElement, { html } from "@/utils/LiteElement"; +import { BtrixElement } from "@/classes/BtrixElement"; type Pages = string[]; type ResponseData = { @@ -33,7 +33,7 @@ const POLL_INTERVAL_SECONDS = 5; */ @localized() @customElement("btrix-crawl-queue") -export class CrawlQueue extends LiteElement { +export class CrawlQueue extends BtrixElement { @property({ type: String }) crawlId?: string; @@ -103,7 +103,7 @@ export class CrawlQueue extends LiteElement { } if (this.pageOffset === 0 && this.queue.total <= this.pageSize) { return msg( - str`Queued URLs from 1 to ${this.queue.total.toLocaleString()}`, + str`Queued URLs from 1 to ${this.localize.number(this.queue.total)}`, ); } @@ -143,8 +143,8 @@ export class CrawlQueue extends LiteElement { this.pageOffset = value - 1; }} > - to ${countMax.toLocaleString()} of - ${this.queue.total.toLocaleString()} + to ${this.localize.number(countMax)} of + ${this.localize.number(this.queue.total)} `)}
`; @@ -173,7 +173,7 @@ export class CrawlQueue extends LiteElement { return html` - ${(idx + this.pageOffset + 1).toLocaleString()}. + ${this.localize.number(idx + this.pageOffset + 1)}. ${this.matchedTotal > 1 - ? msg(str`-${this.matchedTotal.toLocaleString()} URLs`) + ? msg(str`-${this.localize.number(this.matchedTotal)} URLs`) : msg(str`-1 URL`)} ` @@ -255,7 +255,7 @@ export class CrawlQueue extends LiteElement { }, POLL_INTERVAL_SECONDS * 1000); } catch (e) { if ((e as Error).message !== "invalid_regex") { - this.notify({ + this.notify.toast({ message: msg("Sorry, couldn't fetch crawl queue at this time."), variant: "danger", icon: "exclamation-octagon", @@ -282,7 +282,7 @@ export class CrawlQueue extends LiteElement { count, regex, }); - const data: ResponseData = await this.apiFetch( + const data: ResponseData = await this.api.fetch( `/orgs/${this.orgId}/crawls/${this.crawlId}/queue?${params.toString()}`, ); diff --git a/frontend/src/features/browser-profiles/select-browser-profile.ts b/frontend/src/features/browser-profiles/select-browser-profile.ts index abcd1f417..ea870a21e 100644 --- a/frontend/src/features/browser-profiles/select-browser-profile.ts +++ b/frontend/src/features/browser-profiles/select-browser-profile.ts @@ -5,10 +5,9 @@ import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import orderBy from "lodash/fp/orderBy"; +import { BtrixElement } from "@/classes/BtrixElement"; import type { Profile } from "@/pages/org/types"; import type { APIPaginatedList } from "@/types/api"; -import LiteElement from "@/utils/LiteElement"; -import { getLocale } from "@/utils/localization"; type SelectBrowserProfileChangeDetail = { value: Profile | undefined; @@ -31,7 +30,7 @@ export type SelectBrowserProfileChangeEvent = */ @customElement("btrix-select-browser-profile") @localized() -export class SelectBrowserProfile extends LiteElement { +export class SelectBrowserProfile extends BtrixElement { @property({ type: String }) size?: SlSelect["size"]; @@ -86,7 +85,7 @@ export class SelectBrowserProfile extends LiteElement {
${msg("Last updated")} ${msg("Check Profile")} @@ -133,7 +132,7 @@ export class SelectBrowserProfile extends LiteElement { ? html` ${msg("View Profiles")} @@ -171,7 +170,7 @@ export class SelectBrowserProfile extends LiteElement { >${msg("This org doesn't have any custom profiles yet.")} { @@ -235,7 +234,7 @@ export class SelectBrowserProfile extends LiteElement { data, ) as Profile[]; } catch (e) { - this.notify({ + this.notify.toast({ message: msg("Sorry, couldn't retrieve browser profiles at this time."), variant: "danger", icon: "exclamation-octagon", @@ -244,7 +243,7 @@ export class SelectBrowserProfile extends LiteElement { } private async getProfiles() { - const data = await this.apiFetch>( + const data = await this.api.fetch>( `/orgs/${this.orgId}/profiles`, ); diff --git a/frontend/src/features/collections/collection-items-dialog.ts b/frontend/src/features/collections/collection-items-dialog.ts index 81f109a09..e471d4ad7 100644 --- a/frontend/src/features/collections/collection-items-dialog.ts +++ b/frontend/src/features/collections/collection-items-dialog.ts @@ -308,9 +308,11 @@ export class CollectionItemsDialog extends BtrixElement { () => this.showOnlyInCollection ? msg( - str`Crawls in Collection (${data!.total.toLocaleString()})`, + str`Crawls in Collection (${this.localize.number(data!.total)})`, ) - : msg(str`All Workflows (${data!.total.toLocaleString()})`), + : msg( + str`All Workflows (${this.localize.number(data!.total)})`, + ), () => msg("Loading..."), )}
@@ -357,10 +359,10 @@ export class CollectionItemsDialog extends BtrixElement { () => this.showOnlyInCollection ? msg( - str`Uploads in Collection (${this.uploads!.total.toLocaleString()})`, + str`Uploads in Collection (${this.localize.number(this.uploads!.total)})`, ) : msg( - str`All Uploads (${this.uploads!.total.toLocaleString()})`, + str`All Uploads (${this.localize.number(this.uploads!.total)})`, ), () => msg("Loading..."), )} diff --git a/frontend/src/features/collections/collection-workflow-list.ts b/frontend/src/features/collections/collection-workflow-list.ts index 9a813f9cf..7fe50c6fb 100644 --- a/frontend/src/features/collections/collection-workflow-list.ts +++ b/frontend/src/features/collections/collection-workflow-list.ts @@ -15,7 +15,7 @@ import type { } from "@/types/api"; import type { Crawl, Workflow } from "@/types/crawler"; import { finishedCrawlStates } from "@/utils/crawler"; -import { formatNumber, getLocale } from "@/utils/localization"; +import { formatNumber } from "@/utils/localization"; import { pluralOf } from "@/utils/pluralize"; export type SelectionChangeDetail = { @@ -222,11 +222,11 @@ export class CollectionWorkflowList extends BtrixElement { countAsync.then(({ total, selected }) => total === 1 ? msg( - str`${selected.toLocaleString()} / ${total.toLocaleString()} crawl`, + str`${this.localize.number(selected)} / ${this.localize.number(total)} crawl`, ) : total ? msg( - str`${selected.toLocaleString()} / ${total.toLocaleString()} crawls`, + str`${this.localize.number(selected)} / ${this.localize.number(total)} crawls`, ) : msg("0 crawls"), ), @@ -278,7 +278,7 @@ export class CollectionWorkflowList extends BtrixElement {
${pageCount === 1 - ? msg(str`${pageCount.toLocaleString()} page`) - : msg(str`${pageCount.toLocaleString()} pages`)} + ? msg(str`${this.localize.number(pageCount)} page`) + : msg(str`${this.localize.number(pageCount)} pages`)}
${msg(str`Started by ${crawl.userName}`)} diff --git a/frontend/src/features/crawl-workflows/workflow-editor.ts b/frontend/src/features/crawl-workflows/workflow-editor.ts index 114f20e27..fb02b7338 100644 --- a/frontend/src/features/crawl-workflows/workflow-editor.ts +++ b/frontend/src/features/crawl-workflows/workflow-editor.ts @@ -43,6 +43,7 @@ import type { SelectCrawlerProxyChangeEvent } from "@/components/ui/select-crawl import type { Tab } from "@/components/ui/tab-list"; import type { TagInputEvent, TagsChangeEvent } from "@/components/ui/tag-input"; import type { TimeInputChangeEvent } from "@/components/ui/time-input"; +import { getActiveLanguage } from "@/controllers/localize"; import { type SelectBrowserProfileChangeEvent } from "@/features/browser-profiles/select-browser-profile"; import type { CollectionsChangeEvent } from "@/features/collections/collections-add"; import type { QueueExclusionTable } from "@/features/crawl-workflows/queue-exclusion-table"; @@ -65,7 +66,6 @@ import { humanizeSchedule, } from "@/utils/cron"; import { maxLengthValidator } from "@/utils/form"; -import { getLocale } from "@/utils/localization"; import { isArchivingDisabled } from "@/utils/orgs"; import { AppStateService } from "@/utils/state"; import { regexEscape } from "@/utils/string"; @@ -154,8 +154,9 @@ const getDefaultProgressState = (hasConfigId = false): ProgressState => { function getLocalizedWeekDays() { const now = new Date(); - // TODO accept locale from locale-picker - const { format } = new Intl.DateTimeFormat(getLocale(), { weekday: "short" }); + const { format } = new Intl.DateTimeFormat(getActiveLanguage(), { + weekday: "short", + }); return Array.from({ length: 7 }).map((x, day) => format(Date.now() - (now.getDay() - day) * 86400000), ); @@ -873,7 +874,7 @@ https://archiveweb.page/guide`} `)} ${this.renderHelpTextCol( msg( - str`The crawler will visit and record each URL listed here. You can enter up to ${MAX_ADDITIONAL_URLS.toLocaleString()} URLs.`, + str`The crawler will visit and record each URL listed here. You can enter up to ${this.localize.number(MAX_ADDITIONAL_URLS)} URLs.`, ), )} `} @@ -1137,7 +1138,7 @@ https://archiveweb.page/images/${"logo.svg"}`} `)} ${this.renderHelpTextCol( msg( - str`The crawler will visit and record each URL listed here. You can enter up to ${MAX_ADDITIONAL_URLS.toLocaleString()} URLs.`, + str`The crawler will visit and record each URL listed here. You can enter up to ${this.localize.number(MAX_ADDITIONAL_URLS)} URLs.`, ), )}
@@ -1163,11 +1164,11 @@ https://archiveweb.page/images/${"logo.svg"}`} const max = inputEl.max; if (min && value < +min) { helpText = msg( - str`Must be more than minimum of ${(+min).toLocaleString()}`, + str`Must be more than minimum of ${this.localize.number(+min)}`, ); } else if (max && value > +max) { helpText = msg( - str`Must be less than maximum of ${(+max).toLocaleString()}`, + str`Must be less than maximum of ${this.localize.number(+max)}`, ); } } @@ -2060,12 +2061,12 @@ https://archiveweb.page/images/${"logo.svg"}`} let isValid = true; let helpText = urlList.length === 1 - ? msg(str`${urlList.length.toLocaleString()} URL entered`) - : msg(str`${urlList.length.toLocaleString()} URLs entered`); + ? msg(str`${this.localize.number(urlList.length)} URL entered`) + : msg(str`${this.localize.number(urlList.length)} URLs entered`); if (urlList.length > max) { isValid = false; helpText = msg( - str`Please shorten list to ${max.toLocaleString()} or fewer URLs.`, + str`Please shorten list to ${this.localize.number(max)} or fewer URLs.`, ); } else { const invalidUrl = urlList.find((url) => !validURL(url)); diff --git a/frontend/src/features/crawl-workflows/workflow-list.ts b/frontend/src/features/crawl-workflows/workflow-list.ts index 7ecc34f50..5c0d8b3d2 100644 --- a/frontend/src/features/crawl-workflows/workflow-list.ts +++ b/frontend/src/features/crawl-workflows/workflow-list.ts @@ -22,11 +22,12 @@ import { import type { OverflowDropdown } from "@/components/ui/overflow-dropdown"; import { RelativeDuration } from "@/components/ui/relative-duration"; +import { LocalizeController } from "@/controllers/localize"; import { NavigateController } from "@/controllers/navigate"; import type { ListWorkflow } from "@/types/crawler"; import { humanizeSchedule } from "@/utils/cron"; import { srOnly, truncate } from "@/utils/css"; -import { formatNumber, getLocale } from "@/utils/localization"; +import { formatNumber } from "@/utils/localization"; import { pluralOf } from "@/utils/pluralize"; const formatNumberCompact = (v: number) => @@ -215,6 +216,7 @@ export class WorkflowListItem extends LitElement { dropdownMenu!: OverflowDropdown; private readonly navigate = new NavigateController(this); + private readonly localize = new LocalizeController(this); render() { const notSpecified = html` { if (workflow.lastCrawlTime && workflow.lastCrawlStartTime) { return html` html` ${total === this.totalPages ? msg( - str`Showing all ${this.totalPages.toLocaleString()} pages`, + str`Showing all ${this.localize.number(this.totalPages)} pages`, ) : msg( - str`Showing ${total.toLocaleString()} of ${this.totalPages.toLocaleString()} pages`, + str`Showing ${this.localize.number(total)} of ${this.localize.number(this.totalPages)} pages`, )}
diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 673cbb294..4c7cbabe2 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -35,7 +35,6 @@ import { type TranslatedLocaleEnum, } from "@/types/localization"; import { type AppSettings } from "@/utils/app"; -import { LOCALE_PARAM_NAME } from "@/utils/localization"; import brandLockupColor from "~assets/brand/browsertrix-lockup-color.svg"; import "./shoelace"; @@ -889,9 +888,9 @@ export class App extends BtrixElement { onSelectLocale(e: SlSelectEvent) { const locale = e.detail.item.value as TranslatedLocaleEnum; - const url = new URL(window.location.href); - url.searchParams.set(LOCALE_PARAM_NAME, locale); - window.history.pushState(null, "", url.toString()); + if (locale !== this.appState.userPreferences?.language) { + AppStateService.partialUpdateUserPreferences({ language: locale }); + } } onLogOut(event: CustomEvent<{ redirect?: boolean } | null>) { diff --git a/frontend/src/pages/account-settings.ts b/frontend/src/pages/account-settings.ts index 03a484ca4..2b3f6cd50 100644 --- a/frontend/src/pages/account-settings.ts +++ b/frontend/src/pages/account-settings.ts @@ -11,7 +11,7 @@ import debounce from "lodash/fp/debounce"; import { TailwindElement } from "@/classes/TailwindElement"; import needLogin from "@/decorators/needLogin"; import { pageHeader } from "@/layouts/pageHeader"; -import { LanguageCode, translatedLocales } from "@/types/localization"; +import { translatedLocales, type LanguageCode } from "@/types/localization"; import type { UnderlyingFunction } from "@/types/utils"; import { isApiError } from "@/utils/api"; import LiteElement, { html } from "@/utils/LiteElement"; diff --git a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts index 898890c62..1963325e4 100644 --- a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts +++ b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts @@ -30,7 +30,7 @@ import { renderName, } from "@/utils/crawler"; import { humanizeExecutionSeconds } from "@/utils/executionTimeFormatter"; -import { formatNumber, getLocale } from "@/utils/localization"; +import { formatNumber } from "@/utils/localization"; import { isArchivingDisabled } from "@/utils/orgs"; import { pluralOf } from "@/utils/pluralize"; import { tw } from "@/utils/tailwind"; @@ -156,7 +156,7 @@ export class ArchivedItemDetail extends BtrixElement { if (!this.item) return; return html` ${msg("Pages")} ${this.pages != null - ? `(${this.pages.total.toLocaleString()})` + ? `(${this.localize.number(this.pages.total)})` : html``} @@ -343,7 +343,7 @@ export class ArchivedItemDetailQA extends BtrixElement { `, - `${page.notes.length.toLocaleString()} ${pluralOf("comments", page.notes.length)}`, + `${this.localize.number(page.notes.length)} ${pluralOf("comments", page.notes.length)}`, )} ` diff --git a/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts b/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts index a8bed64a6..aa0cd5ee4 100644 --- a/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts +++ b/frontend/src/pages/org/archived-item-qa/archived-item-qa.ts @@ -45,7 +45,7 @@ import { type finishedCrawlStates, } from "@/utils/crawler"; import { maxLengthValidator } from "@/utils/form"; -import { formatISODateString, getLocale } from "@/utils/localization"; +import { formatISODateString } from "@/utils/localization"; import { tw } from "@/utils/tailwind"; const DEFAULT_PAGE_SIZE = 100; @@ -768,7 +768,7 @@ export class ArchivedItemQA extends BtrixElement { (commentCount) => html` - ${msg(str`Comments (${commentCount.toLocaleString()})`)} + ${msg(str`Comments (${this.localize.number(commentCount)})`)}
    ${this.page?.notes?.map( @@ -896,7 +896,7 @@ export class ArchivedItemQA extends BtrixElement { this.page, (page) => html` ${msg("All Resources")}`, html`${crawlResources[TOTAL].good.toLocaleString()}${localizedNumberFormat(crawlResources[TOTAL].good)}`, html`${crawlResources[TOTAL].bad.toLocaleString()}${localizedNumberFormat(crawlResources[TOTAL].bad)}`, html` - ${qaResources[TOTAL].good.toLocaleString()} + ${localizedNumberFormat(qaResources[TOTAL].good)} `, html` - ${qaResources[TOTAL].bad.toLocaleString()} + ${localizedNumberFormat(qaResources[TOTAL].bad)} `, ], ...Object.keys(qaResources) @@ -58,10 +59,10 @@ function renderDiff( .map((key) => [ html`${key}`, html`${Object.prototype.hasOwnProperty.call(crawlResources, key) - ? crawlResources[key].good.toLocaleString() + ? localizedNumberFormat(crawlResources[key].good) : 0}`, html`${Object.prototype.hasOwnProperty.call(crawlResources, key) - ? crawlResources[key].bad.toLocaleString() + ? localizedNumberFormat(crawlResources[key].bad) : 0}`, html` - ${qaResources[key].good.toLocaleString()} + ${localizedNumberFormat(qaResources[key].good)} `, html` - ${qaResources[key].bad.toLocaleString()} + ${localizedNumberFormat(qaResources[key].bad)} `, ]), ]; diff --git a/frontend/src/pages/org/browser-profiles-detail.ts b/frontend/src/pages/org/browser-profiles-detail.ts index 045e0552d..3a12bfac4 100644 --- a/frontend/src/pages/org/browser-profiles-detail.ts +++ b/frontend/src/pages/org/browser-profiles-detail.ts @@ -14,7 +14,7 @@ import type { BrowserConnectionChange } from "@/features/browser-profiles/profil import { pageNav } from "@/layouts/pageHeader"; import { isApiError } from "@/utils/api"; import { maxLengthValidator } from "@/utils/form"; -import { formatNumber, getLocale } from "@/utils/localization"; +import { formatNumber } from "@/utils/localization"; import { isArchivingDisabled } from "@/utils/orgs"; import { pluralOf } from "@/utils/pluralize"; @@ -117,7 +117,7 @@ export class BrowserProfilesDetail extends BtrixElement { ${this.profile ? html` ${this.profile ? html` -

    +

    ${msg("No browser profiles yet.")}

@@ -280,7 +279,7 @@ export class BrowserProfilesList extends BtrixElement { ?disabled=${!data.createdByName} > html`
- ${typeof value === "number" ? value.toLocaleString() : value} + ${typeof value === "number" ? this.localize.number(value) : value}
${when( diff --git a/frontend/src/pages/org/settings/components/billing.ts b/frontend/src/pages/org/settings/components/billing.ts index 5ba2459e9..5d7257686 100644 --- a/frontend/src/pages/org/settings/components/billing.ts +++ b/frontend/src/pages/org/settings/components/billing.ts @@ -12,7 +12,7 @@ import { columns } from "@/layouts/columns"; import { SubscriptionStatus, type BillingPortal } from "@/types/billing"; import type { OrgData, OrgQuotas } from "@/types/org"; import { humanizeSeconds } from "@/utils/executionTimeFormatter"; -import { formatNumber, getLocale } from "@/utils/localization"; +import { formatNumber } from "@/utils/localization"; import { pluralOf } from "@/utils/pluralize"; import { tw } from "@/utils/tailwind"; @@ -127,7 +127,7 @@ export class OrgSettingsBilling extends BtrixElement { ${msg( html`Your plan will be canceled on > = {}; - private readonly dateFormatter = new Intl.DateTimeFormat(getLocale(), { - year: "numeric", - month: "numeric", - day: "numeric", - }); + private readonly dateFormatter = new Intl.DateTimeFormat( + this.localize.activeLanguage, + { + year: "numeric", + month: "numeric", + day: "numeric", + }, + ); private timerId?: number; @@ -239,7 +242,7 @@ export class WorkflowDetail extends LiteElement { // TODO: Check if storage quota has been exceeded here by running // crawl?? } catch (e) { - this.notify({ + this.notify.toast({ message: isApiError(e) && e.statusCode === 404 ? msg("Workflow not found.") @@ -405,7 +408,7 @@ export class WorkflowDetail extends LiteElement { private renderBreadcrumbs() { const breadcrumbs: Breadcrumb[] = [ { - href: `${this.orgBasePath}/workflows`, + href: `${this.navigate.orgBasePath}/workflows`, content: msg("Crawl Workflows"), }, ]; @@ -413,7 +416,7 @@ export class WorkflowDetail extends LiteElement { if (this.isEditing) { breadcrumbs.push( { - href: `${this.orgBasePath}/workflows/${this.workflowId}`, + href: `${this.navigate.orgBasePath}/workflows/${this.workflowId}`, content: this.workflow ? this.renderName() : undefined, }, { @@ -477,7 +480,7 @@ export class WorkflowDetail extends LiteElement { this.crawls, () => html` (${this.crawls!.total.toLocaleString()}${this.workflow + >(${this.localize.number(this.crawls!.total)}${this.workflow ?.isCrawlRunning ? html` + 1` : ""}) - this.navTo( + this.navigate.to( `/orgs/${this.appState.orgSlug}/workflows/${this.workflow?.id}?edit`, )} > @@ -596,7 +599,9 @@ export class WorkflowDetail extends LiteElement { .initialSeeds=${this.seeds!.items} configId=${workflow.id} @reset=${() => - this.navTo(`${this.orgBasePath}/workflows/${workflow.id}`)} + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/${workflow.id}`, + )} > `, this.renderLoading, @@ -717,7 +722,7 @@ export class WorkflowDetail extends LiteElement { - this.navTo( + this.navigate.to( `/orgs/${this.appState.orgSlug}/workflows/${workflow.id}?edit`, )} > @@ -895,7 +900,7 @@ export class WorkflowDetail extends LiteElement { this.crawls!.items.map( (crawl: Crawl) => html` ${when( @@ -1061,10 +1066,10 @@ export class WorkflowDetail extends LiteElement { this.workflow?.lastCrawlId && this.workflow, (workflow) => html` html` ${this.logs?.total - ? this.logs.total.toLocaleString() + ? this.localize.number(this.logs.total) : 0} @@ -1206,7 +1211,7 @@ export class WorkflowDetail extends LiteElement { () => html`

${msg( - str`Displaying latest ${LOGS_PAGE_SIZE.toLocaleString()} errors of ${this.logs!.total.toLocaleString()}.`, + str`Displaying latest ${this.localize.number(LOGS_PAGE_SIZE)} errors of ${this.localize.number(this.logs!.total)}.`, )}

`, @@ -1352,7 +1357,7 @@ export class WorkflowDetail extends LiteElement { this.isSubmittingUpdate = true; try { - const data = await this.apiFetch<{ scaled: boolean }>( + const data = await this.api.fetch<{ scaled: boolean }>( `/orgs/${this.orgId}/crawls/${this.lastCrawlId}/scale`, { method: "POST", @@ -1362,7 +1367,7 @@ export class WorkflowDetail extends LiteElement { if (data.scaled) { void this.fetchWorkflow(); - this.notify({ + this.notify.toast({ message: msg("Updated number of browser windows."), variant: "success", icon: "check2-circle", @@ -1371,7 +1376,7 @@ export class WorkflowDetail extends LiteElement { throw new Error("unhandled API response"); } } catch { - this.notify({ + this.notify.toast({ message: msg( "Sorry, couldn't change number of browser windows at this time.", ), @@ -1384,7 +1389,7 @@ export class WorkflowDetail extends LiteElement { } private async getWorkflow(): Promise { - const data: Workflow = await this.apiFetch( + const data: Workflow = await this.api.fetch( `/orgs/${this.orgId}/crawlconfigs/${this.workflowId}`, ); return data; @@ -1403,7 +1408,7 @@ export class WorkflowDetail extends LiteElement { this.getSeedsPromise = this.getSeeds(); this.seeds = await this.getSeedsPromise; } catch { - this.notify({ + this.notify.toast({ message: msg( "Sorry, couldn't retrieve all crawl settings at this time.", ), @@ -1414,7 +1419,7 @@ export class WorkflowDetail extends LiteElement { } private async getSeeds() { - const data = await this.apiFetch>( + const data = await this.api.fetch>( `/orgs/${this.orgId}/crawlconfigs/${this.workflowId}/seeds`, ); return data; @@ -1424,7 +1429,7 @@ export class WorkflowDetail extends LiteElement { try { this.crawls = await this.getCrawls(); } catch { - this.notify({ + this.notify.toast({ message: msg("Sorry, couldn't get crawls at this time."), variant: "danger", icon: "exclamation-octagon", @@ -1443,7 +1448,7 @@ export class WorkflowDetail extends LiteElement { arrayFormat: "comma", }, ); - const data = await this.apiFetch>( + const data = await this.api.fetch>( `/orgs/${this.orgId}/crawls?${query}`, ); @@ -1468,7 +1473,7 @@ export class WorkflowDetail extends LiteElement { } private async getCrawl(crawlId: Crawl["id"]): Promise { - const data = await this.apiFetch( + const data = await this.api.fetch( `/orgs/${this.orgId}/crawls/${crawlId}/replay.json`, ); @@ -1489,12 +1494,12 @@ export class WorkflowDetail extends LiteElement { name: this.workflow.name ? msg(str`${this.workflow.name} Copy`) : "", }; - this.navTo(`${this.orgBasePath}/workflows/new`, { + this.navigate.to(`${this.navigate.orgBasePath}/workflows/new`, { workflow: workflowParams, seeds: this.seeds?.items, }); - this.notify({ + this.notify.toast({ message: msg(str`Copied Workflow to new template.`), variant: "success", icon: "check2-circle", @@ -1505,16 +1510,16 @@ export class WorkflowDetail extends LiteElement { if (!this.workflow) return; try { - await this.apiFetch( + await this.api.fetch( `/orgs/${this.orgId}/crawlconfigs/${this.workflow.id}`, { method: "DELETE", }, ); - this.navTo(`${this.orgBasePath}/workflows`); + this.navigate.to(`${this.navigate.orgBasePath}/workflows`); - this.notify({ + this.notify.toast({ message: msg( html`Deleted ${this.renderName()} Workflow.`, ), @@ -1522,7 +1527,7 @@ export class WorkflowDetail extends LiteElement { icon: "check2-circle", }); } catch { - this.notify({ + this.notify.toast({ message: msg("Sorry, couldn't delete Workflow at this time."), variant: "danger", icon: "exclamation-octagon", @@ -1536,7 +1541,7 @@ export class WorkflowDetail extends LiteElement { this.isCancelingOrStoppingCrawl = true; try { - const data = await this.apiFetch<{ success: boolean }>( + const data = await this.api.fetch<{ success: boolean }>( `/orgs/${this.orgId}/crawls/${this.lastCrawlId}/cancel`, { method: "POST", @@ -1548,7 +1553,7 @@ export class WorkflowDetail extends LiteElement { throw data; } } catch { - this.notify({ + this.notify.toast({ message: msg("Something went wrong, couldn't cancel crawl."), variant: "danger", icon: "exclamation-octagon", @@ -1564,7 +1569,7 @@ export class WorkflowDetail extends LiteElement { this.isCancelingOrStoppingCrawl = true; try { - const data = await this.apiFetch<{ success: boolean }>( + const data = await this.api.fetch<{ success: boolean }>( `/orgs/${this.orgId}/crawls/${this.lastCrawlId}/stop`, { method: "POST", @@ -1576,7 +1581,7 @@ export class WorkflowDetail extends LiteElement { throw data; } } catch { - this.notify({ + this.notify.toast({ message: msg("Something went wrong, couldn't stop crawl."), variant: "danger", icon: "exclamation-octagon", @@ -1588,7 +1593,7 @@ export class WorkflowDetail extends LiteElement { private async runNow(): Promise { try { - const data = await this.apiFetch<{ started: string | null }>( + const data = await this.api.fetch<{ started: string | null }>( `/orgs/${this.orgId}/crawlconfigs/${this.workflowId}/run`, { method: "POST", @@ -1600,7 +1605,7 @@ export class WorkflowDetail extends LiteElement { void this.fetchWorkflow(); this.goToTab("watch"); - this.notify({ + this.notify.toast({ message: msg("Starting crawl."), variant: "success", icon: "check2-circle", @@ -1622,7 +1627,7 @@ export class WorkflowDetail extends LiteElement { "Your org doesn't have permission to use the proxy configured for this crawl.", ); } - this.notify({ + this.notify.toast({ message: message, variant: "danger", icon: "exclamation-octagon", @@ -1637,7 +1642,7 @@ export class WorkflowDetail extends LiteElement { private async deleteCrawl(crawl: Crawl) { try { - const _data = await this.apiFetch(`/orgs/${crawl.oid}/crawls/delete`, { + const _data = await this.api.fetch(`/orgs/${crawl.oid}/crawls/delete`, { method: "POST", body: JSON.stringify({ crawl_ids: [crawl.id], @@ -1648,7 +1653,7 @@ export class WorkflowDetail extends LiteElement { ...this.crawls!, items: this.crawls!.items.filter((c) => c.id !== crawl.id), }; - this.notify({ + this.notify.toast({ message: msg(`Successfully deleted crawl`), variant: "success", icon: "check2-circle", @@ -1671,7 +1676,7 @@ export class WorkflowDetail extends LiteElement { message = e.message; } } - this.notify({ + this.notify.toast({ message: message, variant: "danger", icon: "exclamation-octagon", @@ -1688,7 +1693,7 @@ export class WorkflowDetail extends LiteElement { if (isApiError(e) && e.statusCode === 503) { // do nothing, keep logs if previously loaded } else { - this.notify({ + this.notify.toast({ message: msg( "Sorry, couldn't retrieve crawl error logs at this time.", ), @@ -1703,7 +1708,7 @@ export class WorkflowDetail extends LiteElement { const page = params.page || this.logs?.page || 1; const pageSize = params.pageSize || this.logs?.pageSize || LOGS_PAGE_SIZE; - const data = await this.apiFetch>( + const data = await this.api.fetch>( `/orgs/${this.orgId}/crawls/${ this.workflow!.lastCrawlId }/errors?page=${page}&pageSize=${pageSize}`, diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts index 2b4fb2685..33e5a5fff 100644 --- a/frontend/src/pages/org/workflows-list.ts +++ b/frontend/src/pages/org/workflows-list.ts @@ -1,6 +1,6 @@ import { localized, msg, str } from "@lit/localize"; import type { SlCheckbox, SlSelectEvent } from "@shoelace-style/shoelace"; -import { type PropertyValues } from "lit"; +import { html, type PropertyValues } from "lit"; import { customElement, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { when } from "lit/directives/when.js"; @@ -14,6 +14,7 @@ import { type WorkflowParams, } from "./types"; +import { BtrixElement } from "@/classes/BtrixElement"; import { CopyButton } from "@/components/ui/copy-button"; import type { PageChangeEvent } from "@/components/ui/pagination"; import { type SelectEvent } from "@/components/ui/search-combobox"; @@ -23,7 +24,6 @@ import scopeTypeLabels from "@/strings/crawl-workflows/scopeType"; import type { APIPaginatedList, APIPaginationQuery } from "@/types/api"; import { NewWorkflowOnlyScopeType } from "@/types/workflow"; import { isApiError } from "@/utils/api"; -import LiteElement, { html } from "@/utils/LiteElement"; import { isArchivingDisabled } from "@/utils/orgs"; import { tw } from "@/utils/tailwind"; @@ -73,7 +73,7 @@ const sortableFields: Record< */ @localized() @customElement("btrix-workflows-list") -export class WorkflowsList extends LiteElement { +export class WorkflowsList extends BtrixElement { static FieldLabels: Record = { name: msg("Name"), firstSeed: msg("Crawl Start URL"), @@ -170,7 +170,7 @@ export class WorkflowsList extends LiteElement { } else if ((e as Error).name === "AbortError") { console.debug("Fetch archived items aborted to throttle"); } else { - this.notify({ + this.notify.toast({ message: msg("Sorry, couldn't retrieve Workflows at this time."), variant: "danger", icon: "exclamation-octagon", @@ -204,11 +204,11 @@ export class WorkflowsList extends LiteElement { () => html` `, )} @@ -221,10 +221,13 @@ export class WorkflowsList extends LiteElement { size="small" ?disabled=${this.org?.readOnly} @click=${() => - this.navTo(`${this.orgBasePath}/workflows/new`, { - scopeType: - this.appState.userPreferences?.newWorkflowScopeType, - })} + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/new`, + { + scopeType: + this.appState.userPreferences?.newWorkflowScopeType, + }, + )} > ${msg("New Workflow")}
${this.renderSearch()}
-
+
${msg("Sort by:")}
- this.navTo(`${this.orgBasePath}/workflows/${workflow.id}#watch`, { - dialog: "scale", - })} + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/${workflow.id}#watch`, + { + dialog: "scale", + }, + )} > ${msg("Edit Browser Windows")} @@ -542,9 +548,12 @@ export class WorkflowsList extends LiteElement { - this.navTo(`${this.orgBasePath}/workflows/${workflow.id}#watch`, { - dialog: "exclusions", - })} + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/${workflow.id}#watch`, + { + dialog: "exclusions", + }, + )} > ${msg("Edit Exclusions")} @@ -558,7 +567,9 @@ export class WorkflowsList extends LiteElement { html` - this.navTo(`${this.orgBasePath}/workflows/${workflow.id}?edit`)} + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/${workflow.id}?edit`, + )} > ${msg("Edit Workflow Settings")} @@ -692,7 +703,7 @@ export class WorkflowsList extends LiteElement { ); this.getWorkflowsController = new AbortController(); - const data = await this.apiFetch>( + const data = await this.api.fetch>( `/orgs/${this.orgId}/crawlconfigs?${query}`, { signal: this.getWorkflowsController.signal, @@ -717,22 +728,22 @@ export class WorkflowsList extends LiteElement { name: workflow.name ? msg(str`${workflow.name} Copy`) : "", }; - this.navTo(`${this.orgBasePath}/workflows/new`, { + this.navigate.to(`${this.navigate.orgBasePath}/workflows/new`, { workflow: workflowParams, seeds: seeds.items, }); if (seeds.total > SEEDS_MAX) { - this.notify({ + this.notify.toast({ title: msg(str`Partially copied Workflow`), message: msg( - str`Only first ${SEEDS_MAX.toLocaleString()} URLs were copied.`, + str`Only first ${this.localize.number(SEEDS_MAX)} URLs were copied.`, ), variant: "warning", icon: "exclamation-triangle", }); } else { - this.notify({ + this.notify.toast({ message: msg(str`Copied Workflow to new template.`), variant: "success", icon: "check2-circle", @@ -742,12 +753,12 @@ export class WorkflowsList extends LiteElement { private async delete(workflow: ListWorkflow): Promise { try { - await this.apiFetch(`/orgs/${this.orgId}/crawlconfigs/${workflow.id}`, { + await this.api.fetch(`/orgs/${this.orgId}/crawlconfigs/${workflow.id}`, { method: "DELETE", }); void this.fetchWorkflows(); - this.notify({ + this.notify.toast({ message: msg( html`Deleted ${this.renderName(workflow)} Workflow.`, ), @@ -755,7 +766,7 @@ export class WorkflowsList extends LiteElement { icon: "check2-circle", }); } catch { - this.notify({ + this.notify.toast({ message: msg("Sorry, couldn't delete Workflow at this time."), variant: "danger", icon: "exclamation-octagon", @@ -766,7 +777,7 @@ export class WorkflowsList extends LiteElement { private async cancel(crawlId: ListWorkflow["lastCrawlId"]) { if (!crawlId) return; if (window.confirm(msg("Are you sure you want to cancel the crawl?"))) { - const data = await this.apiFetch<{ success: boolean }>( + const data = await this.api.fetch<{ success: boolean }>( `/orgs/${this.orgId}/crawls/${crawlId}/cancel`, { method: "POST", @@ -775,7 +786,7 @@ export class WorkflowsList extends LiteElement { if (data.success) { void this.fetchWorkflows(); } else { - this.notify({ + this.notify.toast({ message: msg("Something went wrong, couldn't cancel crawl."), variant: "danger", icon: "exclamation-octagon", @@ -787,7 +798,7 @@ export class WorkflowsList extends LiteElement { private async stop(crawlId: ListWorkflow["lastCrawlId"]) { if (!crawlId) return; if (window.confirm(msg("Are you sure you want to stop the crawl?"))) { - const data = await this.apiFetch<{ success: boolean }>( + const data = await this.api.fetch<{ success: boolean }>( `/orgs/${this.orgId}/crawls/${crawlId}/stop`, { method: "POST", @@ -796,7 +807,7 @@ export class WorkflowsList extends LiteElement { if (data.success) { void this.fetchWorkflows(); } else { - this.notify({ + this.notify.toast({ message: msg("Something went wrong, couldn't stop crawl."), variant: "danger", icon: "exclamation-octagon", @@ -807,21 +818,21 @@ export class WorkflowsList extends LiteElement { private async runNow(workflow: ListWorkflow): Promise { try { - await this.apiFetch( + await this.api.fetch( `/orgs/${this.orgId}/crawlconfigs/${workflow.id}/run`, { method: "POST", }, ); - this.notify({ + this.notify.toast({ message: msg( html`Started crawl from ${this.renderName(workflow)}.
Watch crawl`, ), @@ -850,7 +861,7 @@ export class WorkflowsList extends LiteElement { "Your org doesn't have permission to use the proxy configured for this crawl.", ); } - this.notify({ + this.notify.toast({ message: message, variant: "danger", icon: "exclamation-octagon", @@ -865,7 +876,9 @@ export class WorkflowsList extends LiteElement { names: string[]; descriptions: string[]; firstSeeds: string[]; - } = await this.apiFetch(`/orgs/${this.orgId}/crawlconfigs/search-values`); + } = await this.api.fetch( + `/orgs/${this.orgId}/crawlconfigs/search-values`, + ); // Update search/filter collection const toSearchItem = (key: SearchFields) => (value: string) => ({ @@ -881,7 +894,7 @@ export class WorkflowsList extends LiteElement { } private async getWorkflow(workflow: ListWorkflow): Promise { - const data: Workflow = await this.apiFetch( + const data: Workflow = await this.api.fetch( `/orgs/${this.orgId}/crawlconfigs/${workflow.id}`, ); return data; @@ -889,7 +902,7 @@ export class WorkflowsList extends LiteElement { private async getSeeds(workflow: ListWorkflow) { // NOTE Returns first 1000 seeds (backend pagination max) - const data = await this.apiFetch>( + const data = await this.api.fetch>( `/orgs/${this.orgId}/crawlconfigs/${workflow.id}/seeds`, ); return data; diff --git a/frontend/src/utils/cron.ts b/frontend/src/utils/cron.ts index ad0da87a5..3b41a6891 100644 --- a/frontend/src/utils/cron.ts +++ b/frontend/src/utils/cron.ts @@ -1,8 +1,10 @@ import { parseCron } from "@cheap-glitch/mi-cron"; import { msg, str } from "@lit/localize"; -import { getLocale } from "./localization"; -import * as numberUtils from "./number"; +import { + getActiveLanguage, + localizedNumberFormat, +} from "@/controllers/localize"; export const getNextDate = parseCron.nextDate; @@ -30,12 +32,13 @@ export function humanizeNextDate( schedule: string, options: { length?: "short" } = {}, ): string { + const locale = getActiveLanguage(); const nextDate = parseCron.nextDate(schedule); if (!nextDate) return ""; if (options.length === "short") { - return nextDate.toLocaleString(undefined, { + return nextDate.toLocaleString(locale, { month: "numeric", day: "numeric", year: "numeric", @@ -44,7 +47,7 @@ export function humanizeNextDate( }); } - return nextDate.toLocaleString(undefined, { + return nextDate.toLocaleString(locale, { weekday: "long", month: "long", day: "numeric", @@ -63,6 +66,7 @@ export function humanizeSchedule( schedule: string, options: { length?: "short" } = {}, ): string { + const locale = getActiveLanguage(); const interval = getScheduleInterval(schedule); const parsed = parseCron(schedule); if (!parsed) { @@ -71,7 +75,7 @@ export function humanizeSchedule( } const { days } = parsed; const nextDate = parseCron.nextDate(schedule)!; - const formattedWeekDay = nextDate.toLocaleString(undefined, { + const formattedWeekDay = nextDate.toLocaleString(locale, { weekday: "long", }); @@ -80,7 +84,7 @@ export function humanizeSchedule( if (options.length === "short") { switch (interval) { case "daily": { - const formattedTime = nextDate.toLocaleString(undefined, { + const formattedTime = nextDate.toLocaleString(locale, { minute: "numeric", hour: "numeric", }); @@ -91,9 +95,8 @@ export function humanizeSchedule( intervalMsg = msg(str`Every ${formattedWeekDay}`); break; case "monthly": { - const { format } = numberUtils.numberFormatter(getLocale()); intervalMsg = msg( - str`Monthly on the ${format(days[0], { ordinal: true })}`, + str`Monthly on the ${localizedNumberFormat(days[0], { ordinal: true })}`, ); break; @@ -103,7 +106,7 @@ export function humanizeSchedule( break; } } else { - const formattedTime = nextDate.toLocaleString(undefined, { + const formattedTime = nextDate.toLocaleString(locale, { minute: "numeric", hour: "numeric", timeZoneName: "short", diff --git a/frontend/src/utils/executionTimeFormatter.ts b/frontend/src/utils/executionTimeFormatter.ts index 52cf16633..bb353bfd5 100644 --- a/frontend/src/utils/executionTimeFormatter.ts +++ b/frontend/src/utils/executionTimeFormatter.ts @@ -1,6 +1,6 @@ import { html, nothing } from "lit"; -import { getLocale } from "./localization"; +import { getActiveLanguage } from "@/controllers/localize"; /** * Returns either `nothing`, or hours-minutes-seconds wrapped in parens. @@ -90,7 +90,7 @@ export const humanizeExecutionSeconds = ( displaySeconds = false, round = "up", } = options || {}; - const locale = getLocale(); + const locale = getActiveLanguage(); const minutes = round === "down" ? Math.floor(seconds / 60) : Math.ceil(seconds / 60); diff --git a/frontend/src/utils/localization.ts b/frontend/src/utils/localization.ts index 08e373164..7fd77ea3f 100644 --- a/frontend/src/utils/localization.ts +++ b/frontend/src/utils/localization.ts @@ -1,12 +1,5 @@ -import { sourceLocale } from "@/__generated__/locale-codes"; +import { getActiveLanguage } from "@/controllers/localize"; import type { LanguageCode } from "@/types/localization"; -import appState from "@/utils/state"; - -export const LOCALE_PARAM_NAME = "locale" as const; - -export const getLocale = () => { - return appState.userLanguage || getLang() || sourceLocale; -}; /** * Get time zone short name from locales @@ -28,19 +21,19 @@ export const pluralize = ( number: number, strings: { [k in Intl.LDMLPluralRule]: string }, options?: Intl.PluralRulesOptions, -) => strings[new Intl.PluralRules(getLocale(), options).select(number)]; +) => strings[new Intl.PluralRules(getActiveLanguage(), options).select(number)]; export const formatNumber = ( number: number, options?: Intl.NumberFormatOptions, -) => new Intl.NumberFormat(getLocale(), options).format(number); +) => new Intl.NumberFormat(getActiveLanguage(), options).format(number); export const formatISODateString = ( date: string, // ISO string options?: Intl.DateTimeFormatOptions, ) => new Date(date.endsWith("Z") ? date : `${date}Z`).toLocaleDateString( - getLocale(), + getActiveLanguage(), { month: "2-digit", day: "2-digit", diff --git a/frontend/src/utils/slugify.ts b/frontend/src/utils/slugify.ts index da3c0febb..b257e4a9f 100644 --- a/frontend/src/utils/slugify.ts +++ b/frontend/src/utils/slugify.ts @@ -1,7 +1,11 @@ import slugify from "slugify"; -import { getLocale } from "./localization"; +import { getActiveLanguage } from "@/controllers/localize"; export default function slugifyStrict(value: string) { - return slugify(value, { strict: true, lower: true, locale: getLocale() }); + return slugify(value, { + strict: true, + lower: true, + locale: getActiveLanguage(), + }); } diff --git a/frontend/src/utils/workflow.ts b/frontend/src/utils/workflow.ts index 47340d106..c3c219a52 100644 --- a/frontend/src/utils/workflow.ts +++ b/frontend/src/utils/workflow.ts @@ -5,6 +5,7 @@ import { getAppSettings } from "./app"; import { getLang } from "./localization"; import type { Tags } from "@/components/ui/tag-input"; +import { localizedNumberFormat } from "@/controllers/localize"; import { ScopeType, type Profile, @@ -38,7 +39,7 @@ export function defaultLabel(value: unknown): string { return msg("Default: Unlimited"); } if (typeof value === "number") { - return msg(str`Default: ${value.toLocaleString()}`); + return msg(str`Default: ${localizedNumberFormat(value)}`); } if (value) { return msg(str`Default: ${value}`); From 9dba4d93930682c97255ccd9df70d95a1ad83480 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 12 Nov 2024 15:14:48 -0800 Subject: [PATCH 3/6] localize bytes --- frontend/src/components/orgs-list.ts | 9 ++++++++- frontend/src/components/ui/config-details.ts | 13 +++++++------ frontend/src/components/ui/file-list.ts | 9 ++++++++- .../features/archived-items/archived-item-list.ts | 1 + frontend/src/features/archived-items/crawl-list.ts | 1 + .../collections/collection-workflow-list.ts | 1 + .../src/features/crawl-workflows/workflow-list.ts | 5 +++++ .../archived-item-detail/archived-item-detail.ts | 6 +++++- frontend/src/pages/org/collection-detail.ts | 1 + frontend/src/pages/org/collections-list.ts | 1 + frontend/src/pages/org/dashboard.ts | 13 ++++++++++++- .../src/pages/org/settings/components/billing.ts | 1 + frontend/src/pages/org/workflow-detail.ts | 2 ++ 13 files changed, 53 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/orgs-list.ts b/frontend/src/components/orgs-list.ts index 91654750d..fb0eff8f9 100644 --- a/frontend/src/components/orgs-list.ts +++ b/frontend/src/components/orgs-list.ts @@ -320,7 +320,10 @@ export class OrgsList extends BtrixElement { ${msg( html`Deleting an org will delete all - + of data associated with the org.`, )} @@ -330,6 +333,7 @@ export class OrgsList extends BtrixElement { ${msg( html`Crawls: `, )} @@ -338,6 +342,7 @@ export class OrgsList extends BtrixElement { ${msg( html`Uploads: `, )} @@ -346,6 +351,7 @@ export class OrgsList extends BtrixElement { ${msg( html`Profiles: `, )} @@ -638,6 +644,7 @@ export class OrgsList extends BtrixElement { ${org.bytesStored ? html`` diff --git a/frontend/src/components/ui/config-details.ts b/frontend/src/components/ui/config-details.ts index 671373f8d..71c579978 100644 --- a/frontend/src/components/ui/config-details.ts +++ b/frontend/src/components/ui/config-details.ts @@ -1,6 +1,6 @@ import { localized, msg, str } from "@lit/localize"; import ISO6391 from "iso-639-1"; -import { nothing } from "lit"; +import { html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { when } from "lit/directives/when.js"; import { html as staticHtml, unsafeStatic } from "lit/static-html.js"; @@ -9,6 +9,7 @@ import RegexColorize from "regex-colorize"; import { RelativeDuration } from "./relative-duration"; +import { BtrixElement } from "@/classes/BtrixElement"; import type { CrawlConfig, Seed, SeedConfig } from "@/pages/org/types"; import scopeTypeLabel from "@/strings/crawl-workflows/scopeType"; import sectionStrings from "@/strings/crawl-workflows/section"; @@ -18,7 +19,6 @@ import { isApiError } from "@/utils/api"; import { getAppSettings } from "@/utils/app"; import { DEPTH_SUPPORTED_SCOPES, isPageScopeType } from "@/utils/crawler"; import { humanizeSchedule } from "@/utils/cron"; -import LiteElement, { html } from "@/utils/LiteElement"; import { formatNumber } from "@/utils/localization"; import { pluralOf } from "@/utils/pluralize"; @@ -32,7 +32,7 @@ import { pluralOf } from "@/utils/pluralize"; */ @localized() @customElement("btrix-config-details") -export class ConfigDetails extends LiteElement { +export class ConfigDetails extends BtrixElement { @property({ type: Object }) crawlConfig?: CrawlConfig; @@ -94,6 +94,7 @@ export class ConfigDetails extends LiteElement { // Eventually we will want to set this to the selected locale if (valueBytes) { return html``; @@ -209,7 +210,7 @@ export class ConfigDetails extends LiteElement { href=${`/orgs/${crawlConfig!.oid}/browser-profiles/profile/${ crawlConfig!.profileid }`} - @click=${this.navLink} + @click=${this.navigate.link} > ${crawlConfig?.profileName} `, @@ -483,7 +484,7 @@ export class ConfigDetails extends LiteElement { try { await this.getCollections(); } catch (e) { - this.notify({ + this.notify.toast({ message: isApiError(e) && e.statusCode === 404 ? msg("Collections not found.") @@ -503,7 +504,7 @@ export class ConfigDetails extends LiteElement { if (this.crawlConfig?.autoAddCollections && orgId) { for (const collectionId of this.crawlConfig.autoAddCollections) { - const data = await this.apiFetch( + const data = await this.api.fetch( `/orgs/${orgId}/collections/${collectionId}`, ); if (data) { diff --git a/frontend/src/components/ui/file-list.ts b/frontend/src/components/ui/file-list.ts index bd1e7a10e..72fb0bdff 100644 --- a/frontend/src/components/ui/file-list.ts +++ b/frontend/src/components/ui/file-list.ts @@ -7,6 +7,7 @@ import { } from "lit/decorators.js"; import { TailwindElement } from "@/classes/TailwindElement"; +import { LocalizeController } from "@/controllers/localize"; import { truncate } from "@/utils/css"; type FileRemoveDetail = { @@ -74,6 +75,8 @@ export class FileListItem extends TailwindElement { @property({ type: Boolean }) progressIndeterminate?: boolean; + private readonly localize = new LocalizeController(this); + render() { if (!this.file) return; return html`
@@ -83,10 +86,14 @@ export class FileListItem extends TailwindElement {
${this.progressValue !== undefined ? html` / ` - : ""} + : ""}
diff --git a/frontend/src/features/archived-items/archived-item-list.ts b/frontend/src/features/archived-items/archived-item-list.ts index bd06e52a9..70786682e 100644 --- a/frontend/src/features/archived-items/archived-item-list.ts +++ b/frontend/src/features/archived-items/archived-item-list.ts @@ -251,6 +251,7 @@ export class ArchivedItemListItem extends TailwindElement { @click=${this.onTooltipClick} > diff --git a/frontend/src/features/collections/collection-workflow-list.ts b/frontend/src/features/collections/collection-workflow-list.ts index 7fe50c6fb..819d8f349 100644 --- a/frontend/src/features/collections/collection-workflow-list.ts +++ b/frontend/src/features/collections/collection-workflow-list.ts @@ -292,6 +292,7 @@ export class CollectionWorkflowList extends BtrixElement {
diff --git a/frontend/src/features/crawl-workflows/workflow-list.ts b/frontend/src/features/crawl-workflows/workflow-list.ts index 5c0d8b3d2..38e1813fb 100644 --- a/frontend/src/features/crawl-workflows/workflow-list.ts +++ b/frontend/src/features/crawl-workflows/workflow-list.ts @@ -315,12 +315,14 @@ export class WorkflowListItem extends LitElement { workflow.lastCrawlSize ) { return html` + @@ -328,6 +330,7 @@ export class WorkflowListItem extends LitElement { } if (workflow.totalSize && workflow.lastCrawlSize) { return html``; @@ -335,6 +338,7 @@ export class WorkflowListItem extends LitElement { if (workflow.isCrawlRunning && workflow.lastCrawlSize) { return html` @@ -342,6 +346,7 @@ export class WorkflowListItem extends LitElement { } if (workflow.totalSize) { return html``; diff --git a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts index 1963325e4..937e7f356 100644 --- a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts +++ b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts @@ -833,6 +833,7 @@ export class ArchivedItemDetail extends BtrixElement { ${this.item ? html`${this.item.fileSize ? html` `, )} - +
`, diff --git a/frontend/src/pages/org/collection-detail.ts b/frontend/src/pages/org/collection-detail.ts index 598b1f701..486922c23 100644 --- a/frontend/src/pages/org/collection-detail.ts +++ b/frontend/src/pages/org/collection-detail.ts @@ -475,6 +475,7 @@ export class CollectionDetail extends BtrixElement { msg("Total Size"), (col) => html``, diff --git a/frontend/src/pages/org/collections-list.ts b/frontend/src/pages/org/collections-list.ts index 21a1c30e3..c80d5cd2c 100644 --- a/frontend/src/pages/org/collections-list.ts +++ b/frontend/src/pages/org/collections-list.ts @@ -530,6 +530,7 @@ export class CollectionsList extends BtrixElement {
diff --git a/frontend/src/pages/org/dashboard.ts b/frontend/src/pages/org/dashboard.ts index 52b91be42..7e75478a2 100644 --- a/frontend/src/pages/org/dashboard.ts +++ b/frontend/src/pages/org/dashboard.ts @@ -148,6 +148,7 @@ export class Dashboard extends BtrixElement { secondaryValue: hasQuota ? "" : html``, singleLabel: msg("Crawl"), @@ -162,6 +163,7 @@ export class Dashboard extends BtrixElement { secondaryValue: hasQuota ? "" : html``, singleLabel: msg("Upload"), @@ -173,6 +175,7 @@ export class Dashboard extends BtrixElement { secondaryValue: hasQuota ? "" : html``, singleLabel: msg("Browser Profile"), @@ -190,6 +193,7 @@ export class Dashboard extends BtrixElement { secondaryValue: hasQuota ? "" : html``, singleLabel: msg("Archived Item"), @@ -274,7 +278,11 @@ export class Dashboard extends BtrixElement {
${label}
- + | ${this.renderPercentage(value / metrics.storageUsedBytes)}
@@ -297,6 +305,7 @@ export class Dashboard extends BtrixElement { hasQuota ? html` @@ -351,11 +360,13 @@ export class Dashboard extends BtrixElement {
diff --git a/frontend/src/pages/org/settings/components/billing.ts b/frontend/src/pages/org/settings/components/billing.ts index 5d7257686..213ecd075 100644 --- a/frontend/src/pages/org/settings/components/billing.ts +++ b/frontend/src/pages/org/settings/components/billing.ts @@ -304,6 +304,7 @@ export class OrgSettingsBilling extends BtrixElement { ${msg( html`${quotas.storageQuota ? html`` : msg("Unlimited")} diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts index 52ddecd45..a7fce1eea 100644 --- a/frontend/src/pages/org/workflow-detail.ts +++ b/frontend/src/pages/org/workflow-detail.ts @@ -777,6 +777,7 @@ export class WorkflowDetail extends BtrixElement { msg("Total Size"), (workflow) => html` `, @@ -972,6 +973,7 @@ export class WorkflowDetail extends BtrixElement { ${this.renderDetailItem(msg("Crawl Size"), () => this.workflow ? html`` From 389e4c7ecf10c49466d5c17ede67434a60f341fe Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 12 Nov 2024 15:23:28 -0800 Subject: [PATCH 4/6] remove unused user language --- frontend/src/components/ui/user-language-select.ts | 2 +- frontend/src/controllers/localize.ts | 4 +++- frontend/src/index.ts | 2 +- frontend/src/utils/state.ts | 4 ---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ui/user-language-select.ts b/frontend/src/components/ui/user-language-select.ts index 1869230d0..47c81ad80 100644 --- a/frontend/src/components/ui/user-language-select.ts +++ b/frontend/src/components/ui/user-language-select.ts @@ -38,7 +38,7 @@ export class LocalePicker extends BtrixElement { } render() { - const selectedLocale = this.appState.userLanguage || sourceLocale; + const selectedLocale = this.localize.activeLanguage; return html` ` : html` ${this.renderSignUpLink()} - ${(translatedLocales as unknown as string[]).length > 1 + ${(translatedLocales as unknown as string[]).length > 2 ? html` Date: Tue, 12 Nov 2024 15:44:24 -0800 Subject: [PATCH 5/6] fix timezone --- frontend/src/components/ui/user-language-select.ts | 1 - .../pages/org/archived-item-detail/archived-item-detail.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ui/user-language-select.ts b/frontend/src/components/ui/user-language-select.ts index 47c81ad80..5d6699790 100644 --- a/frontend/src/components/ui/user-language-select.ts +++ b/frontend/src/components/ui/user-language-select.ts @@ -2,7 +2,6 @@ import type { SlSelectEvent } from "@shoelace-style/shoelace"; import { html } from "lit"; import { customElement, state } from "lit/decorators.js"; -import { sourceLocale } from "@/__generated__/locale-codes"; import { BtrixElement } from "@/classes/BtrixElement"; import { translatedLocales, diff --git a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts index 937e7f356..e61f8bb33 100644 --- a/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts +++ b/frontend/src/pages/org/archived-item-detail/archived-item-detail.ts @@ -163,7 +163,7 @@ export class ArchivedItemDetail extends BtrixElement { year="2-digit" hour="numeric" minute="numeric" - timeZoneName="short" + time-zone-name="short" >`; } @@ -782,7 +782,7 @@ export class ArchivedItemDetail extends BtrixElement { year="2-digit" hour="numeric" minute="numeric" - timeZoneName="short" + time-zone-name="short" > From 2133f4e39bd31a08ada025852bb8a26a23d1ccfe Mon Sep 17 00:00:00 2001 From: SuaYoo Date: Tue, 12 Nov 2024 23:45:53 +0000 Subject: [PATCH 6/6] Apply `localize:extract` changes --- frontend/xliff/es.xlf | 92 +++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/frontend/xliff/es.xlf b/frontend/xliff/es.xlf index 9cecfcd50..1cac8d6bc 100644 --- a/frontend/xliff/es.xlf +++ b/frontend/xliff/es.xlf @@ -151,7 +151,7 @@ Total Pages - pages + pages Last Updated @@ -350,7 +350,7 @@ Add “ - + URLs + + URLs (unnamed item) @@ -362,7 +362,7 @@ Every - Monthly on the + Monthly on the Every day at @@ -378,7 +378,7 @@ Default: Unlimited - Default: + Default: Specify exclusion rules for what pages should not be visited. @@ -1015,7 +1015,7 @@ Logs will show here after you run a crawl. - Displaying latest errors of . + Displaying latest errors of . Upcoming Pages @@ -1165,7 +1165,7 @@ Partially copied Workflow - Only first URLs were copied. + Only first URLs were copied. Are you sure you want to cancel the crawl? @@ -1968,10 +1968,10 @@ Text Match - Showing all pages + Showing all pages - Showing of pages + Showing of pages No matching pages found @@ -2091,7 +2091,7 @@ Submit Review - Comments () + Comments () commented on @@ -2368,23 +2368,6 @@ ? This cannot be undone. - - Deleting an org will delete all - - of data associated with the org. - - - Crawls: - - - - Uploads: - - - - Profiles: - - Type "" to confirm @@ -2515,7 +2498,7 @@ QA Analysis Runs - page + page Duration @@ -2563,14 +2546,14 @@ Queued URLs - Queued URLs from 1 to + Queued URLs from 1 to Queued URLs from - to of - + to of + No pages queued. @@ -2582,7 +2565,7 @@ Load more - - URLs + - URLs -1 URL @@ -2654,10 +2637,10 @@ Create profile - / crawl + / crawl - / crawls + / crawls 0 crawls @@ -2678,19 +2661,19 @@ in - Crawls in Collection () + Crawls in Collection () - All Workflows () + All Workflows () Loading... - Uploads in Collection () + Uploads in Collection () - All Uploads () + All Uploads () In Collection? @@ -2817,10 +2800,10 @@ Specify exclusion rules for what pages should not be visited. - Must be more than minimum of + Must be more than minimum of - Must be less than maximum of + Must be less than maximum of Auto-scroll behavior @@ -2916,13 +2899,13 @@ Couldn't save Workflow. Please fix the following Workflow issues: - URL entered + URL entered - URLs entered + URLs entered - Please shorten list to or fewer URLs. + Please shorten list to or fewer URLs. Please remove or fix the following invalid URL: @@ -3544,7 +3527,7 @@ Time AM/PM - + storage @@ -3603,7 +3586,7 @@ Started crawl from . - Watch crawl + Watch crawl Workflow settings used to run this crawl @@ -3625,7 +3608,7 @@ Your plan will be canceled on - + Scope @@ -3682,7 +3665,7 @@ The URL of the page to crawl. - The crawler will visit and record each URL listed here. You can enter up to URLs. + The crawler will visit and record each URL listed here. You can enter up to URLs. If checked, the crawler will visit pages one link away. @@ -3798,6 +3781,23 @@ Choose your preferred language for displaying Browsertrix in your browser. + + Deleting an org will delete all + + of data associated with the org. + + + Crawls: + + + + Uploads: + + + + Profiles: + +