mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: homepage
This commit is contained in:
1 parent
1d44ecab18
commit
d9688f6051
18 files changed
+303
-153
No files matched your search
+138
-1
@@ -1,5 +1,18 @@
|
||||
{
|
||||
"test": {
|
||||
"$": "test",
|
||||
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is %s1 because it has the color of %s2. I like it so much that I will say its name 3 times! %s1! %s1! %s1! Once again, it's %s2, not %s3.",
|
||||
"fox": "The quick brown fox jumps over the lazy dog."
|
||||
},
|
||||
|
||||
"common": {
|
||||
"branches": {
|
||||
"local": "dev",
|
||||
"dev": "beta",
|
||||
"opentest": "opentest",
|
||||
"preview": "preview"
|
||||
},
|
||||
|
||||
"locales": {
|
||||
"en": "English",
|
||||
"es": "Spanish",
|
||||
@@ -689,5 +702,129 @@
|
||||
}
|
||||
},
|
||||
|
||||
"website": {}
|
||||
"website": {
|
||||
"home": {
|
||||
"hero": {
|
||||
"credit": "By %s1 ❤️"
|
||||
},
|
||||
"hot_tanks": {
|
||||
"title": "Popular tanks",
|
||||
"subtitle": "Last %s1 days"
|
||||
},
|
||||
"other_projects": {
|
||||
"title": "Other cool projects"
|
||||
}
|
||||
},
|
||||
|
||||
"tools": {
|
||||
"tankopedia": {
|
||||
"name": "Tankopedia",
|
||||
"button": "Find tanks",
|
||||
"description": "Blitz tank encyclopedia",
|
||||
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz"
|
||||
},
|
||||
"compare": {
|
||||
"name": "Compare",
|
||||
"button": "Compare tanks",
|
||||
"description": "Compare tank statistics",
|
||||
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz"
|
||||
},
|
||||
"performance": {
|
||||
"name": "Performance",
|
||||
"button": "View tanks",
|
||||
"description": "Tank performance statistics",
|
||||
"seo_description": "Best tanks and their performances in World of Tanks Blitz"
|
||||
},
|
||||
"gallery": {
|
||||
"name": "Gallery",
|
||||
"button": "Browse all",
|
||||
"description": "Blitz player avatars",
|
||||
"seo_description": "All World of Tanks Blitz player avatars"
|
||||
},
|
||||
"session": {
|
||||
"name": "Session",
|
||||
"button": "Start tracking",
|
||||
"description": "Live session stats",
|
||||
"seo_description": "Track your stats in real time as you play World of Tanks Blitz"
|
||||
},
|
||||
"tier_list": {
|
||||
"name": "Tier list",
|
||||
"button": "Rank tanks",
|
||||
"description": "Rank Blitz tanks",
|
||||
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz"
|
||||
},
|
||||
"embed": {
|
||||
"name": "Embed",
|
||||
"button": "Create now",
|
||||
"description": "Embeds for streaming",
|
||||
"seo_description": "Create embeds for streaming World of Tanks Blitz with extreme customization"
|
||||
},
|
||||
"charts": {
|
||||
"name": "Charts",
|
||||
"button": "Nerd out",
|
||||
"description": "Lots of graphs",
|
||||
"seo_description": "Charts and graphs on anything and everything about tanks, players, and events in World of Tanks Blitz"
|
||||
},
|
||||
"rating": {
|
||||
"name": "Rating",
|
||||
"button": "View leaderboard",
|
||||
"description": "Full rating leaderboard",
|
||||
"seo_description": "Live and archived full rating leaderboard for World of Tanks Blitz"
|
||||
},
|
||||
"discord": {
|
||||
"name": "Discord bot",
|
||||
"button": "Install now",
|
||||
"description": "Stats right in Discord",
|
||||
"seo_description": "Get stats right in Discord with BlitzKit's Discord bot"
|
||||
},
|
||||
"more": {
|
||||
"name": "More coming soon",
|
||||
"button": "Join Discord",
|
||||
"description": "What we're making",
|
||||
"seo_description": "What we're making with BlitzKit"
|
||||
},
|
||||
"players": {
|
||||
"name": "Players",
|
||||
"button": "Lookup player",
|
||||
"description": "Periodical player statistics",
|
||||
"seo_description": "Periodical player statistics in World of Tanks Blitz"
|
||||
},
|
||||
"home": {
|
||||
"name": "Home",
|
||||
"button": "Get started",
|
||||
"description": "Get started with BlitzKit",
|
||||
"seo_description": "Get started with BlitzKit"
|
||||
},
|
||||
"blitz_analysis": {
|
||||
"name": "BlitzAnalysis[]",
|
||||
"button": "Analyze now",
|
||||
"description": "Rich historic average stats",
|
||||
"seo_description": "Rich historic average stats in World of Tanks Blitz"
|
||||
},
|
||||
"blitz_insider": {
|
||||
"name": "Blitz Insider",
|
||||
"button": "Discover today",
|
||||
"description": "In-game session stats",
|
||||
"seo_description": "In-game session stats in World of Tanks Blitz"
|
||||
}
|
||||
},
|
||||
|
||||
"footer": {
|
||||
"motto": "Everything World of Tanks Blitz",
|
||||
"copyright": "Unaffiliated with Wargaming or any service",
|
||||
|
||||
"about": {
|
||||
"title": "About",
|
||||
"credits": "Credits",
|
||||
"discord": "Discord",
|
||||
"github": "GitHub",
|
||||
"patches": "Patches"
|
||||
},
|
||||
"legal": {
|
||||
"title": "Legal",
|
||||
"privacy": "Privacy policy",
|
||||
"terms": "Terms of service"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,2 @@
|
||||
export const SUPPORTED_LOCALES = [
|
||||
'en',
|
||||
'es',
|
||||
'hr',
|
||||
'pt',
|
||||
'ru',
|
||||
'uk',
|
||||
'zh',
|
||||
] as const;
|
||||
|
||||
export const SUPPORTED_LOCALES = ['en', 'es', 'hr', 'pt', 'ru', 'uk', 'zh'];
|
||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0];
|
||||
|
||||
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './strings';
|
||||
export * from './translator';
|
||||
@@ -0,0 +1,46 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import type en from '../../lang/en.json';
|
||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../blitzkit';
|
||||
import { TranslationTree } from './translator';
|
||||
|
||||
const DEBUG_MISSING_I18N = import.meta.env.DEBUG_MISSING_I18N;
|
||||
const files = import.meta.glob('../../lang/*.json', { eager: true });
|
||||
export const localizedStrings: Record<string, typeof en> = {};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
SUPPORTED_LOCALES.forEach((locale) => {
|
||||
const defaultStrings = files[`../../lang/${DEFAULT_LOCALE}.json`];
|
||||
const strings = files[`../../lang/${locale}.json`];
|
||||
const mergedStrings = merge({}, defaultStrings, strings);
|
||||
|
||||
if (DEBUG_MISSING_I18N === 'true') nuke(mergedStrings);
|
||||
|
||||
localizedStrings[locale] = mergedStrings as typeof en;
|
||||
});
|
||||
|
||||
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('');
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
export type TranslationTree = { [key: string]: TranslationNode };
|
||||
|
||||
export type TranslationNode = string | TranslationTree;
|
||||
|
||||
type NestedKeys<Type> = Type extends object
|
||||
? {
|
||||
[Key in keyof Type]: Key extends string
|
||||
? Type[Key] extends object
|
||||
? '$' extends keyof Type[Key]
|
||||
? `${Key}` | `${Key}.${NestedKeys<Type[Key]>}`
|
||||
: `${Key}.${NestedKeys<Type[Key]>}`
|
||||
: Key
|
||||
: never;
|
||||
}[keyof Type]
|
||||
: never;
|
||||
|
||||
export class Translator<
|
||||
Locale extends string,
|
||||
DefaultLocale extends Locale,
|
||||
Strings extends TranslationTree,
|
||||
Path extends NestedKeys<Strings>,
|
||||
> {
|
||||
private readonly defaultStrings: Map<string, string>;
|
||||
|
||||
constructor(
|
||||
private strings: Record<Locale, Strings>,
|
||||
private defaultLocale: DefaultLocale,
|
||||
) {
|
||||
this.defaultStrings = this.flatten(strings[defaultLocale]);
|
||||
}
|
||||
|
||||
private flatten(strings: TranslationTree) {
|
||||
const translations = new Map<string, string>();
|
||||
|
||||
function traverse(parent: string, tree: Record<string, TranslationNode>) {
|
||||
for (const key in tree) {
|
||||
const path = `${parent}${key}`;
|
||||
|
||||
if (typeof tree[key] === 'string') {
|
||||
translations.set(path, tree[key]);
|
||||
} else {
|
||||
traverse(`${path}.`, tree[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse('', strings);
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
locale(locale: Locale) {
|
||||
if (this.strings[locale] === undefined) {
|
||||
throw new TypeError(`Locale "${locale}" is not defined`);
|
||||
}
|
||||
|
||||
const strings = this.strings[locale];
|
||||
const defaultStrings = this.defaultStrings;
|
||||
const defaultLocale = this.defaultLocale;
|
||||
|
||||
const translations = this.flatten(strings);
|
||||
|
||||
function translate(path: Path, literals?: string[]) {
|
||||
let translation = translations.get(path) ?? translations.get(`${path}.$`);
|
||||
|
||||
if (translation === undefined) {
|
||||
translation =
|
||||
defaultStrings.get(path) ?? defaultStrings.get(`${path}.$`);
|
||||
|
||||
if (translation === undefined) {
|
||||
throw new Error(
|
||||
`Undefined translation for "${path}" in locale "${locale}"; no fallback found in "${defaultLocale}"`,
|
||||
);
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`Undefined translation for "${path}" in locale "${locale}"; falling back to "${defaultLocale}"`,
|
||||
);
|
||||
}
|
||||
|
||||
literals?.forEach((literal, index) => {
|
||||
translation = translation!.replaceAll(`%s${index + 1}`, literal);
|
||||
});
|
||||
|
||||
return translation;
|
||||
}
|
||||
|
||||
return { translate, t: translate };
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './blitz';
|
||||
export * from './blitzkit';
|
||||
export * from './databases';
|
||||
export * from './i18n';
|
||||
export * from './imgur';
|
||||
export * from './math';
|
||||
export * from './patreon';
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
Reference in new issue
Block a user