diff --git a/bun.lockb b/bun.lockb index 466a6e303..90fba2261 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/core/src/blitzkit/tankNames.ts b/packages/core/src/blitzkit/tankNames.ts index 26dea0712..57018ee21 100644 --- a/packages/core/src/blitzkit/tankNames.ts +++ b/packages/core/src/blitzkit/tankNames.ts @@ -1,8 +1,8 @@ -import { deburr } from 'lodash-es'; -import { SUPPORTED_LOCALES } from '../../../i18n/src/strings'; -import { I18nString } from '../protos'; -import { fetchCamouflageDefinitions } from './camouflageDefinitions'; -import { fetchTankDefinitions } from './tankDefinitions'; +import locales from "@blitzkit/i18n/locales.json"; +import { deburr } from "lodash-es"; +import { I18nString } from "../protos"; +import { fetchCamouflageDefinitions } from "./camouflageDefinitions"; +import { fetchTankDefinitions } from "./tankDefinitions"; export async function fetchTankNames() { const [tankDefinitions, camouflageDefinitions] = await Promise.all([ @@ -26,25 +26,27 @@ export async function fetchTankNames() { searchableNameDeburr, camouflages: tank.camouflages ?.map((id) => - SUPPORTED_LOCALES.map( - (locale) => - camouflageDefinitions.camouflages[id]?.name.locales[locale], - ), + locales.supported.map( + ({ locale }) => + camouflageDefinitions.camouflages[id]?.name.locales[locale] + ) ) .flat() .filter(Boolean) .map(deburr) - .join(' '), + .join(" "), treeType: tank.type, }; - }), + }) ); } export const SEARCH_KEYS = [ - ...SUPPORTED_LOCALES.map((locale) => [ - `searchableName.locales.${locale}`, - `searchableNameDeburr.locales.${locale}`, - ]).flat(), - 'camouflages', + ...locales.supported + .map(({ locale }) => [ + `searchableName.locales.${locale}`, + `searchableNameDeburr.locales.${locale}`, + ]) + .flat(), + "camouflages", ]; diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index 1d7e6b8b6..9fb52c844 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -1,3 +1,4 @@ -export * from './literals'; -export * from './strings'; -export * from './unwrapper'; +export * from "./literals"; +export * from "./strings"; +export * from "./unwrapper"; +export * from "./wrapper"; diff --git a/packages/i18n/src/strings.ts b/packages/i18n/src/strings.ts index 25c022efd..386979308 100644 --- a/packages/i18n/src/strings.ts +++ b/packages/i18n/src/strings.ts @@ -1,98 +1,66 @@ -import { merge } from 'ts-deepmerge'; -import en from '../strings/en.json'; -import es from '../strings/es.json'; -import fr from '../strings/fr.json'; -import ja from '../strings/ja.json'; -import pl from '../strings/pl.json'; -import pt from '../strings/pt.json'; -import ru from '../strings/ru.json'; -import uk from '../strings/uk.json'; -import zh from '../strings/zh.json'; +import { merge } from "ts-deepmerge"; +import locales from "../locales.json"; +import en from "../strings/en.json"; +import es from "../strings/es.json"; +import fr from "../strings/fr.json"; +import ja from "../strings/ja.json"; +import pl from "../strings/pl.json"; +import pt from "../strings/pt.json"; +import ru from "../strings/ru.json"; +import uk from "../strings/uk.json"; +import zh from "../strings/zh.json"; export type BlitzKitStrings = typeof en; -const stringsRaw = { en, es, fr, ja, pl, pt, ru, uk, zh }; +const stringsRaw: Record = { + en, + es, + fr, + ja, + pl, + pt, + ru, + uk, + zh, +}; -export const SUPPORTED_LOCALES = Object.keys(stringsRaw) as SupportedLocale[]; - -export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0]; - -function redact(string: string) { - const matches = [...string.matchAll(/%s\d+/g)]; - const characters = string - .split('') - .map((char) => (char === ' ' ? ' ' : 'β–ˆ')) as string[]; - - matches.forEach((match) => { - const start = match.index; - const matchText = match[0]; - for (let i = 0; i < matchText.length; i++) { - characters[start + i] = matchText[i]; - } - }); - - return characters.join(''); -} - -function nuke(object: TranslationTree) { - for (const key in object) { - const typedKey = key as keyof typeof object; - - if (typeof object[typedKey] === 'string') { - const value = object[typedKey]; - object[typedKey] = redact(value as string); - } else nuke(object[typedKey] as TranslationTree); - } -} - -const stringsPartial: Partial> = {}; +export const STRINGS: Record = {}; for (const locale in stringsRaw) { - if (locale === DEFAULT_LOCALE) { - stringsPartial[DEFAULT_LOCALE] = stringsRaw[DEFAULT_LOCALE]; + if (locale === locales.default) { + STRINGS[locales.default] = stringsRaw[locales.default] as BlitzKitStrings; continue; } - const localeStrings = stringsRaw[locale as SupportedLocale]; - stringsPartial[locale as SupportedLocale] = merge( - stringsRaw[DEFAULT_LOCALE], - localeStrings, - ); - - if (import.meta.env.PUBLIC_DEBUG_MISSING_I18N === 'true') { - nuke(stringsPartial[locale as SupportedLocale]!); - } + const localeStrings = stringsRaw[locale]; + STRINGS[locale] = merge( + stringsRaw[locales.default], + localeStrings + ) as BlitzKitStrings; } -export const STRINGS = stringsPartial as Record< - SupportedLocale, - BlitzKitStrings ->; - -export type SupportedLocale = keyof typeof stringsRaw; - -export const SUPPORTED_LOCALE_BLITZ_MAP: Record = { - en: 'en', - es: 'es', - fr: 'fr', - ja: 'ja', - pt: 'pt', - ru: 'ru', - uk: 'uk', - zh: 'zh-Hans', - pl: 'pl', +export const SUPPORTED_LOCALE_BLITZ_MAP: Record = { + en: "en", + es: "es", + fr: "fr", + ja: "ja", + pt: "pt", + ru: "ru", + uk: "uk", + zh: "zh-Hans", + pl: "pl", }; -export const SUPPORTED_LOCALE_FLAGS: Record = { - en: 'πŸ‡ΊπŸ‡Έ', - es: 'πŸ‡ͺπŸ‡Έ', - fr: 'πŸ‡«πŸ‡·', - ja: 'πŸ‡―πŸ‡΅', - pt: 'πŸ‡΅πŸ‡Ή', - ru: 'πŸ‡·πŸ‡Ί', - uk: 'πŸ‡ΊπŸ‡¦', - zh: 'πŸ‡¨πŸ‡³', - pl: 'πŸ‡΅πŸ‡±', +export const SUPPORTED_LOCALE_FLAGS: Record = { + en: "πŸ‡ΊπŸ‡Έ", + es: "πŸ‡ͺπŸ‡Έ", + fr: "πŸ‡«πŸ‡·", + ja: "πŸ‡―πŸ‡΅", + pt: "πŸ‡΅πŸ‡Ή", + ru: "πŸ‡·πŸ‡Ί", + uk: "πŸ‡ΊπŸ‡¦", + zh: "πŸ‡¨πŸ‡³", + pl: "πŸ‡΅πŸ‡±", }; export type TranslationTree = { [key: string]: TranslationNode }; diff --git a/packages/i18n/src/wrapper.ts b/packages/i18n/src/wrapper.ts new file mode 100644 index 000000000..544435136 --- /dev/null +++ b/packages/i18n/src/wrapper.ts @@ -0,0 +1,15 @@ +import locales from "../locales.json"; +import { BlitzKitStrings, STRINGS } from "./strings"; + +export function wrapper(slicer: (strings: BlitzKitStrings) => string) { + const defaultString = slicer(STRINGS[locales.default] as BlitzKitStrings); + + const object = { [locales.default]: defaultString }; + + for (const { locale } of locales.supported) { + const string = slicer(STRINGS[locale] as BlitzKitStrings); + if (string !== defaultString) object[locale] = string; + } + + return object; +} diff --git a/packages/website/astro.config.mjs b/packages/website/astro.config.mjs deleted file mode 100644 index 1cc967379..000000000 --- a/packages/website/astro.config.mjs +++ /dev/null @@ -1,30 +0,0 @@ -// @ts-check -import strings from "@blitzkit/i18n/strings/en.json"; -import favicons from "astro-favicons"; -import { defineConfig } from "astro/config"; - -import react from "@astrojs/react"; - -export default defineConfig({ - devToolbar: { enabled: false }, - - integrations: [ - favicons({ - name: strings.common.name, - short_name: strings.common.name, - - manifest: { start_url: "/", display_override: ["minimal-ui"] }, - - icons: { - windows: true, - android: true, - appleIcon: true, - appleStartup: true, - favicons: true, - yandex: true, - }, - }), - - react(), - ], -}); diff --git a/packages/website/astro.config.ts b/packages/website/astro.config.ts new file mode 100644 index 000000000..54bad5eed --- /dev/null +++ b/packages/website/astro.config.ts @@ -0,0 +1,67 @@ +// @ts-check +import strings from "@blitzkit/i18n/strings/en.json"; +import AstroPWA from "@vite-pwa/astro"; +import favicons from "astro-favicons"; +import { defineConfig } from "astro/config"; + +import react from "@astrojs/react"; +import { wrapper } from "@blitzkit/i18n"; +import { mauve, mauveDark } from "@radix-ui/colors"; +import { tools } from "./src/constants/tools"; + +export default defineConfig({ + devToolbar: { enabled: false }, + + integrations: [ + favicons({ + name: strings.common.name, + short_name: strings.common.name, + + background: mauveDark.mauve1, + themes: [mauve.mauve1, mauveDark.mauve1], + appleStatusBarStyle: "black-translucent", + + shortcuts: Object.values(tools) + .filter((tool) => !tool.disabled && !tool.href) + .map((tool) => { + const stringId = (tool.strings ?? + tool.id) as keyof typeof strings.website.tools; + + return { + name: strings.website.tools[stringId].name, + name_localized: wrapper( + (strings) => strings.website.tools[stringId].name + ), + description: strings.website.tools[stringId].description, + description_localized: wrapper( + (strings) => strings.website.tools[stringId].description + ), + short_name: strings.website.tools[stringId].name, + short_name_localized: wrapper( + (strings) => strings.website.tools[stringId].name + ), + url: `/${tool.id}`, + }; + }), + + manifest: { + start_url: "/", + display: "fullscreen", + display_override: ["fullscreen", "standalone", "minimal-ui"], + }, + + icons: { + windows: true, + android: true, + appleIcon: true, + appleStartup: true, + favicons: true, + yandex: true, + }, + }), + + AstroPWA({ manifest: false }), + + react(), + ], +}); diff --git a/packages/website/package.json b/packages/website/package.json index 3312821c6..b760100a3 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -9,8 +9,10 @@ }, "dependencies": { "@astrojs/react": "^4.4.0", + "@radix-ui/colors": "^3.0.0", "@types/react": "^19.1.14", "@types/react-dom": "^19.1.9", + "@vite-pwa/astro": "^1.1.0", "astro": "^5.14.1", "astro-favicons": "^3.1.5", "react": "^19.1.1", diff --git a/packages/website/public/favicon.svg b/packages/website/public/favicon.svg index f153bc7be..a89c21cb9 100644 --- a/packages/website/public/favicon.svg +++ b/packages/website/public/favicon.svg @@ -1,47 +1,46 @@ - - - - - - - - + + + + + + + + + + + + + + + - + - - + + - - + + - - - - - + - - - + + + - - - + + + - - - - - - - + + + diff --git a/packages/website/src/components/LocaleSwitcher.tsx b/packages/website/src/components/LocaleSwitcher.tsx index 9e949e944..d53ed9e2f 100644 --- a/packages/website/src/components/LocaleSwitcher.tsx +++ b/packages/website/src/components/LocaleSwitcher.tsx @@ -1,17 +1,16 @@ -import {} from '@blitzkit/core'; import { - DEFAULT_LOCALE, STRINGS, - SUPPORTED_LOCALES, SUPPORTED_LOCALE_FLAGS, -} from '@blitzkit/i18n'; -import { Select } from '@radix-ui/themes'; -import type { LocaleAcceptorProps } from '../hooks/useLocale'; -import { BlitzKitTheme } from './BlitzKitTheme'; + type BlitzKitStrings, +} from "@blitzkit/i18n"; +import locales from "@blitzkit/i18n/locales.json"; +import { Select } from "@radix-ui/themes"; +import type { LocaleAcceptorProps } from "../hooks/useLocale"; +import { BlitzKitTheme } from "./BlitzKitTheme"; export function LocaleSwitcherThemeWrapper({ locale }: LocaleAcceptorProps) { return ( - + ); @@ -22,29 +21,33 @@ export function LocaleSwitcher({ locale }: LocaleAcceptorProps) { { - localStorage.setItem('preferred-locale', locale); + localStorage.setItem("preferred-locale", locale); let rawPath = window.location.pathname; - for (const supportedLocale of SUPPORTED_LOCALES) { - if (window.location.pathname.startsWith(`/${supportedLocale}`)) { - rawPath = rawPath.replace(`/${supportedLocale}`, ''); + for (const supported of locales.supported) { + if (window.location.pathname.startsWith(`/${supported.locale}`)) { + rawPath = rawPath.replace(`/${supported.locale}`, ""); break; } } window.location.pathname = `${ - locale === DEFAULT_LOCALE ? '' : `/${locale}` + locale === locales.default ? "" : `/${locale}` }${rawPath}`; }} > - {SUPPORTED_LOCALES.map((locale) => ( + {locales.supported.map(({ locale }) => ( - {SUPPORTED_LOCALE_FLAGS[locale]}{' '} - {STRINGS[locale].common.locales[locale]} + {SUPPORTED_LOCALE_FLAGS[locale]}{" "} + { + STRINGS[locale].common.locales[ + locale as keyof BlitzKitStrings["common"]["locales"] + ] + } ))} diff --git a/packages/website/src/core/blitzkit/branches.ts b/packages/website/src/core/blitzkit/branches.ts index 2b3c5a9b3..1861f11c8 100644 --- a/packages/website/src/core/blitzkit/branches.ts +++ b/packages/website/src/core/blitzkit/branches.ts @@ -1,15 +1,16 @@ -import { assertSecret } from '@blitzkit/core'; -import { DEFAULT_LOCALE, STRINGS, type SupportedLocale } from '@blitzkit/i18n'; +import { assertSecret } from "@blitzkit/core"; +import { STRINGS } from "@blitzkit/i18n"; +import locales from "@blitzkit/i18n/locales.json"; -export function resolveBranchName(locale: string = DEFAULT_LOCALE) { +export function resolveBranchName(locale: string = locales.default) { if (!(locale in STRINGS)) throw new Error(`Unsupported locale: ${locale}`); - const strings = STRINGS[locale as SupportedLocale]; + const strings = STRINGS[locale]; const secret = assertSecret(import.meta.env.PUBLIC_BRANCH); if ( - assertSecret(import.meta.env.MODE) === 'development' && - secret === 'dev' + assertSecret(import.meta.env.MODE) === "development" && + secret === "dev" ) { return strings.common.branches.local; } else { diff --git a/packages/website/src/core/i18n/getStrings.ts b/packages/website/src/core/i18n/getStrings.ts index ccd3480bc..85f30f873 100644 --- a/packages/website/src/core/i18n/getStrings.ts +++ b/packages/website/src/core/i18n/getStrings.ts @@ -1,6 +1,7 @@ -import { DEFAULT_LOCALE, STRINGS, type SupportedLocale } from '@blitzkit/i18n'; +import { STRINGS } from "@blitzkit/i18n"; +import locales from "@blitzkit/i18n/locales.json"; -export function getStrings(locale: string = DEFAULT_LOCALE) { - if (locale in STRINGS) return STRINGS[locale as SupportedLocale]; +export function getStrings(locale: string = locales.default) { + if (locale in STRINGS) return STRINGS[locale]; throw new Error(`Unsupported locale: ${locale}`); } diff --git a/packages/website/src/pages/[...locale]/index.astro b/packages/website/src/pages/[...locale]/index.astro index 56d35daed..af513a94d 100644 --- a/packages/website/src/pages/[...locale]/index.astro +++ b/packages/website/src/pages/[...locale]/index.astro @@ -1,7 +1,7 @@ --- import { AstroSeo } from "@astrolib/seo"; import { assertSecret } from "@blitzkit/core"; -import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "@blitzkit/i18n"; +import locales from "@blitzkit/i18n/locales.json"; import { literals } from "@blitzkit/i18n/src/literals"; import { Box, Flex, Link } from "@radix-ui/themes"; import type { GetStaticPaths } from "astro"; @@ -16,7 +16,7 @@ import BaseLayout from "../../layouts/BaseLayout.astro"; const MAX_TOOLS_PER_ROW = 2; const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH); -const locale = Astro.params.locale ?? DEFAULT_LOCALE; +const locale = Astro.params.locale ?? locales.default; const strings = getStrings(Astro.params.locale); const rows: Tool[][] = []; @@ -40,9 +40,9 @@ for (const id in tools) { export const getStaticPaths = (() => { return [ - ...SUPPORTED_LOCALES.filter((locale) => locale !== DEFAULT_LOCALE).map( - (locale) => ({ params: { locale } }) - ), + ...locales.supported + .filter(({ locale }) => locale !== locales.default) + .map(({ locale }) => ({ params: { locale } })), { params: { locale: undefined } }, ]; }) satisfies GetStaticPaths; @@ -81,7 +81,7 @@ export const getStaticPaths = (() => { --> { - locale !== DEFAULT_LOCALE && ( + locale !== locales.default && ( { > {literals(strings.website.home.help_translate, [ "BlitzKit", - strings.common.locales[locale], + strings.common.locales[ + locale as keyof typeof strings.common.locales + ], ])}