refactor: move languages to contracts (@fehmer) (#6497)

This commit is contained in:
Christian Fehmer 2025-05-07 14:02:27 +02:00 committed by GitHub
parent ed24f7f45b
commit eb092cea1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 1296 additions and 1698 deletions

View file

@ -188,7 +188,7 @@ describe("Loaderboard Controller", () => {
expect(body).toEqual({
message: "Invalid query schema",
validationErrors: [
'"language" Can only contain letters [a-zA-Z0-9_+]',
'"language" Invalid enum value. Must be a supported language',
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
'"page" Number must be greater than or equal to 0',
@ -344,7 +344,7 @@ describe("Loaderboard Controller", () => {
expect(body).toEqual({
message: "Invalid query schema",
validationErrors: [
'"language" Can only contain letters [a-zA-Z0-9_+]',
'"language" Invalid enum value. Must be a supported language',
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
],
@ -652,7 +652,7 @@ describe("Loaderboard Controller", () => {
expect(body).toEqual({
message: "Invalid query schema",
validationErrors: [
'"language" Can only contain letters [a-zA-Z0-9_+]',
'"language" Invalid enum value. Must be a supported language',
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
],
@ -830,7 +830,7 @@ describe("Loaderboard Controller", () => {
expect(body).toEqual({
message: "Invalid query schema",
validationErrors: [
'"language" Can only contain letters [a-zA-Z0-9_+]',
'"language" Invalid enum value. Must be a supported language',
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
],

View file

@ -88,7 +88,7 @@ describe("PublicController", () => {
expect(body).toEqual({
message: "Invalid query schema",
validationErrors: [
'"language" Can only contain letters [a-zA-Z0-9_+]',
'"language" Invalid enum value. Must be a supported language',
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
],

View file

@ -22,6 +22,7 @@ import { roundTo2 } from "@monkeytype/util/numbers";
import { MonkeyRequest } from "../types";
import { DBResult } from "../../utils/result";
import { LbPersonalBests } from "../../utils/pb";
import { Language } from "@monkeytype/contracts/schemas/languages";
const CREATE_RESULT_DEFAULT_OPTIONS = {
firstTestTimestamp: DateUtils.startOfDay(new UTCDate(Date.now())).valueOf(),
@ -207,7 +208,7 @@ async function updateUser(uid: string): Promise<void> {
const modes = stats.map(
(it) =>
it["_id"] as {
language: string;
language: Language;
mode: "time" | "custom" | "words" | "quote" | "zen";
mode2: `${number}` | "custom" | "zen";
}

View file

@ -23,6 +23,7 @@ import {
} from "@monkeytype/contracts/quotes";
import { replaceObjectId, replaceObjectIds } from "../../utils/misc";
import { MonkeyRequest } from "../types";
import { Language } from "@monkeytype/contracts/schemas/languages";
async function verifyCaptcha(captcha: string): Promise<void> {
if (!(await verify(captcha))) {
@ -36,9 +37,9 @@ export async function getQuotes(
const { uid } = req.ctx.decodedToken;
const quoteMod = (await getPartialUser(uid, "get quotes", ["quoteMod"]))
.quoteMod;
const quoteModString = quoteMod === true ? "all" : (quoteMod as string);
const quoteModLanguage = quoteMod === true ? "all" : (quoteMod as Language);
const data = await NewQuotesDAL.get(quoteModString);
const data = await NewQuotesDAL.get(quoteModLanguage);
return new MonkeyResponse(
"Quote submissions retrieved",
replaceObjectIds(data)

View file

@ -11,6 +11,7 @@ import { WithObjectId } from "../utils/misc";
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
import { z } from "zod";
import { tryCatchSync } from "@monkeytype/util/trycatch";
import { Language } from "@monkeytype/contracts/schemas/languages";
const JsonQuoteSchema = z.object({
text: z.string(),
@ -112,11 +113,11 @@ export async function add(
return undefined;
}
export async function get(language: string): Promise<DBNewQuote[]> {
export async function get(language: Language | "all"): Promise<DBNewQuote[]> {
if (git === undefined) throw new MonkeyError(500, "Git not available.");
const where: {
approved: boolean;
language?: string;
language?: Language;
} = {
approved: false,
};

View file

@ -2,6 +2,7 @@ import { QuoteRating } from "@monkeytype/contracts/schemas/quotes";
import * as db from "../init/db";
import { Collection } from "mongodb";
import { WithObjectId } from "../utils/misc";
import { Language } from "@monkeytype/contracts/schemas/languages";
type DBQuoteRating = WithObjectId<QuoteRating>;
@ -11,7 +12,7 @@ export const getQuoteRatingCollection = (): Collection<DBQuoteRating> =>
export async function submit(
quoteId: number,
language: string,
language: Language,
rating: number,
update: boolean
): Promise<void> {
@ -47,7 +48,7 @@ export async function submit(
export async function get(
quoteId: number,
language: string
language: Language
): Promise<DBQuoteRating | null> {
return await getQuoteRatingCollection().findOne({ quoteId, language });
}

View file

@ -59,7 +59,7 @@ const permissionChecks: Record<PermissionId, PermissionCheck> = {
["quoteMod"],
(user) =>
user.quoteMod === true ||
(typeof user.quoteMod === "string" && user.quoteMod !== "")
(typeof user.quoteMod === "string" && (user.quoteMod as string) !== "")
),
canReport: buildUserPermission(
["canReport"],

View file

@ -31,35 +31,28 @@ The contents of the file should be as follows:
It is recommended that you familiarize yourselves with JSON before adding a language. For the `name` field, put the name of your language. `rightToLeft` indicates how the language is written. If it is written right to left then put `true`, otherwise put `false`.
`ligatures` A ligature occurs when multiple letters are joined together to form a character [more details](<https://en.wikipedia.org/wiki/Ligature_(writing)>). If there's joining in the words, which is the case in languages like (Arabic, Malayalam, Persian, Sanskrit, Central_Kurdish... etc.), then set the value to `true`, otherwise set it to `false`. For `bcp47` put your languages [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag). If the words you're adding are ordered by frequency (most common words at the top, least at the bottom) set the value of `orderedByFrequency` to `true`, otherwise `false`. Finally, add your list of words to the `words` field.
In addition to the language file, you need to add your language to the `_groups.json` and `_list.json` files in the same directory. Add the name of the language to the `_groups.json` file like so:
Then, go to `packages/contracts/src/schemas/languages.ts` and add your new language name at the _end_ of the `LanguageSchema` enum. Make sure to end the line with a comma. Make sure to add all your language names if you have created multiple word lists of differing lengths in the same language.
```json
{
"name": "spanish",
"languages": ["spanish", "spanish_1k", "spanish_10k"]
},
{
"name": "YOUR_LANGUAGE",
"languages": ["YOUR_LANGUAGES"]
},
{
"name": "french",
"languages": ["french", "french_1k", "french_2k", "french_10k"]
},
```typescript
export const LanguageSchema = z.enum([
"english",
"english_1k",
...
"your_language_name",
"your_language_name_10k",
]);
```
The `languages` field is the list of files that you have created for your language (without the `.json` file extension). Make sure to add all your files if you have created multiple word lists of differing lengths in the same language.
Then, go to `frontend/src/ts/constants/language.ts` and add your new language name to the `LanguageGroups` map. You can either add it to an existing group or add a new one. Make sure to add all your language names if you have created multiple word lists of differing lengths in the same language.
Add your language lists to the `_list.json` file like so:
```json
,"spanish"
,"spanish_1k"
,"spanish_10k"
,"YOUR_LANGUAGE"
,"french"
,"french_1k"
,"french_2k"
```typescript
export const LanguageGroups: Record<string, Language[]> = {
...
your_language_name: [
"your_language_name",
"your_language_name_10k",
]
};
```
### Committing Languages

View file

@ -0,0 +1,67 @@
import { readdirSync } from "fs";
import { LanguageGroups, LanguageList } from "../../src/ts/constants/languages";
import { Language } from "@monkeytype/contracts/schemas/languages";
describe("languages", () => {
describe("LanguageList", () => {
it("should not have duplicates", () => {
const duplicates = LanguageList.filter(
(item, index) => LanguageList.indexOf(item) !== index
);
expect(duplicates).toEqual([]);
});
it("should have all related json files", () => {
const languageFiles = listLanguageFiles();
const missingLanguageFiles = LanguageList.filter(
(it) => !languageFiles.includes(it)
).map((it) => `fontend/static/languages/${it}.json`);
expect(missingLanguageFiles, "missing language json files").toEqual([]);
});
it("should not have additional related json files", () => {
const LanguageFiles = listLanguageFiles();
const additionalLanguageFiles = LanguageFiles.filter(
(it) => !LanguageList.some((language) => language === it)
).map((it) => `fontend/static/languages/${it}.json`);
expect(
additionalLanguageFiles,
"additional language json files not declared in frontend/src/ts/constants/languages.ts"
).toEqual([]);
});
});
describe("LanguageGroups", () => {
it("should contain each language once", () => {
const languagesWithMultipleGroups = [];
const groupByLanguage = new Map<Language, string>();
for (const group of Object.keys(LanguageGroups)) {
for (const language of LanguageGroups[group] as Language[]) {
if (groupByLanguage.has(language)) {
languagesWithMultipleGroups.push(language);
}
groupByLanguage.set(language, group);
}
}
expect(
languagesWithMultipleGroups,
"languages with multiple groups"
).toEqual([]);
expect(
Array.from(groupByLanguage.keys()).sort(),
"every language has a group"
).toEqual(LanguageList.sort());
});
});
});
function listLanguageFiles() {
return readdirSync(import.meta.dirname + "/../../static/languages").map(
(it) => it.substring(0, it.length - 5) as Language
);
}

View file

@ -423,11 +423,8 @@ describe("Config", () => {
it("setLanguage", () => {
expect(Config.setLanguage("english")).toBe(true);
expect(Config.setLanguage("english_1k")).toBe(true);
expect(Config.setLanguage(stringOfLength(50))).toBe(true);
expect(Config.setLanguage("english 1k")).toBe(false);
expect(Config.setLanguage("english-1k")).toBe(false);
expect(Config.setLanguage(stringOfLength(51))).toBe(false);
expect(Config.setLanguage("invalid" as any)).toBe(false);
});
it("setKeymapLayout", () => {
expect(Config.setKeymapLayout("overrideSync")).toBe(true);

View file

@ -391,58 +391,9 @@ function validateQuotes() {
function validateLanguages() {
return new Promise((resolve, reject) => {
//languages
const languagesData = JSON.parse(
fs.readFileSync("./static/languages/_list.json", {
encoding: "utf8",
flag: "r",
})
);
const languagesSchema = {
type: "array",
items: {
type: "string",
},
};
const languagesValidator = ajv.compile(languagesSchema);
if (languagesValidator(languagesData)) {
console.log("Languages list JSON schema is \u001b[32mvalid\u001b[0m");
} else {
console.log("Languages list JSON schema is \u001b[31minvalid\u001b[0m");
return reject(new Error(languagesValidator.errors[0].message));
}
//languages group
const languagesGroupData = JSON.parse(
fs.readFileSync("./static/languages/_groups.json", {
encoding: "utf8",
flag: "r",
})
);
const languagesGroupSchema = {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
languages: {
type: "array",
items: {
type: "string",
},
},
},
required: ["name", "languages"],
},
};
const languagesGroupValidator = ajv.compile(languagesGroupSchema);
if (languagesGroupValidator(languagesGroupData)) {
console.log("Languages groups JSON schema is \u001b[32mvalid\u001b[0m");
} else {
console.log("Languages groups JSON schema is \u001b[31minvalid\u001b[0m");
return reject(new Error(languagesGroupValidator.errors[0].message));
}
const languages = fs
.readdirSync("./static/languages")
.map((it) => it.substring(0, it.length - 5));
//language files
const languageFileSchema = {
type: "object",
@ -472,7 +423,7 @@ function validateLanguages() {
let languageFilesErrors;
const duplicatePercentageThreshold = 0.0001;
let langsWithDuplicates = 0;
languagesData.forEach((language) => {
languages.forEach((language) => {
const languageFileData = JSON.parse(
fs.readFileSync(`./static/languages/${language}.json`, {
encoding: "utf8",

View file

@ -504,14 +504,28 @@
<i class="fas fa-gamepad"></i>
funbox
</div>
<div class="select filterGroup" group="funbox"></div>
<div class="select filterGroup" group="funbox">
<select
class="funboxSelect"
group="funbox"
placeholder="select a funbox"
multiple
></select>
</div>
</div>
<div class="buttonsAndTitle languages">
<div class="title">
<i class="fas fa-globe-americas"></i>
language
</div>
<div class="select filterGroup" group="language"></div>
<div class="select filterGroup" group="language">
<select
class="languageSelect"
group="language"
placeholder="select a language"
multiple
></select>
</div>
</div>
</div>
</div>

View file

@ -84,9 +84,7 @@ import LoadChallengeCommands, {
import FontFamilyCommands, {
update as updateFontFamilyCommands,
} from "./lists/font-family";
import LanguagesCommands, {
update as updateLanguagesCommands,
} from "./lists/languages";
import LanguagesCommands from "./lists/languages";
import KeymapLayoutsCommands from "./lists/keymap-layouts";
import Config, * as UpdateConfig from "../config";
@ -107,17 +105,7 @@ import {
import { Command, CommandsSubgroup } from "./types";
import * as TestLogic from "../test/test-logic";
import * as ActivePage from "../states/active-page";
const languagesPromise = JSONData.getLanguageList();
languagesPromise
.then((languages) => {
updateLanguagesCommands(languages);
})
.catch((e: unknown) => {
console.error(
Misc.createErrorMessage(e, "Failed to update language commands")
);
});
import { Language } from "@monkeytype/contracts/schemas/languages";
const fontsPromise = JSONData.getFontsList();
fontsPromise
@ -226,7 +214,7 @@ export const commands: CommandsSubgroup = {
icon: "fa-language",
exec: ({ input }): void => {
if (input === undefined) return;
void UpdateConfig.setCustomPolyglot(input.split(" "));
void UpdateConfig.setCustomPolyglot(input.split(" ") as Language[]);
if (ActivePage.get() === "test") {
TestLogic.restart();
}
@ -490,7 +478,8 @@ export function doesListExist(listName: string): boolean {
export async function getList(
listName: ListsObjectKeys
): Promise<CommandsSubgroup> {
await Promise.allSettled([languagesPromise, fontsPromise, challengesPromise]);
await Promise.allSettled([fontsPromise, challengesPromise]);
const list = lists[listName];
if (!list) {
Notifications.add(`List not found: ${listName}`, -1);
@ -531,8 +520,7 @@ export function getTopOfStack(): CommandsSubgroup {
let singleList: CommandsSubgroup | undefined;
export async function getSingleSubgroup(): Promise<CommandsSubgroup> {
await Promise.allSettled([languagesPromise, fontsPromise, challengesPromise]);
await Promise.allSettled([fontsPromise, challengesPromise]);
const singleCommands: Command[] = [];
for (const command of commands.list) {
const ret = buildSingleListCommands(command);

View file

@ -1,4 +1,5 @@
import * as UpdateConfig from "../../config";
import { LanguageList } from "../../constants/languages";
import {
capitalizeFirstLetterOfEachWord,
getLanguageDisplayString,
@ -8,12 +9,14 @@ import { Command, CommandsSubgroup } from "../types";
const subgroup: CommandsSubgroup = {
title: "Language...",
configKey: "language",
list: [
{
id: "couldnotload",
display: "Could not load the languages list :(",
list: LanguageList.map((language) => ({
id: "changeLanguage" + capitalizeFirstLetterOfEachWord(language),
display: getLanguageDisplayString(language),
configValue: language,
exec: (): void => {
UpdateConfig.setLanguage(language);
},
],
})),
};
const commands: Command[] = [
@ -25,19 +28,4 @@ const commands: Command[] = [
},
];
function update(languages: string[]): void {
subgroup.list = [];
languages.forEach((language) => {
subgroup.list.push({
id: "changeLanguage" + capitalizeFirstLetterOfEachWord(language),
display: getLanguageDisplayString(language),
configValue: language,
exec: (): void => {
UpdateConfig.setLanguage(language);
},
});
});
}
export default commands;
export { update };

View file

@ -24,7 +24,10 @@ import {
import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs";
import { Config, FunboxName } from "@monkeytype/contracts/schemas/configs";
import { Mode, ModeSchema } from "@monkeytype/contracts/schemas/shared";
import { Language, LanguageSchema } from "@monkeytype/contracts/schemas/util";
import {
Language,
LanguageSchema,
} from "@monkeytype/contracts/schemas/languages";
import { LocalStorageWithSchema } from "./utils/local-storage-with-schema";
import { migrateConfig } from "./utils/config";
import { roundTo1 } from "@monkeytype/util/numbers";

View file

@ -14,6 +14,7 @@ import {
TestActivityCalendar,
} from "../elements/test-activity-calendar";
import { Preset } from "@monkeytype/contracts/schemas/presets";
import { Language } from "@monkeytype/contracts/schemas/languages";
export type SnapshotUserTag = UserTag & {
active?: boolean;
@ -43,7 +44,7 @@ export type SnapshotResult<M extends Mode> = Omit<
lazyMode: boolean;
difficulty: string;
funbox: FunboxName[];
language: string;
language: Language;
numbers: boolean;
punctuation: boolean;
quoteLength: number;

View file

@ -0,0 +1,366 @@
import {
Language,
LanguageSchema,
} from "@monkeytype/contracts/schemas/languages";
export const LanguageList: Language[] = LanguageSchema._def.values;
export const LanguageGroups: Record<string, Language[]> = {
english: [
"english",
"english_1k",
"english_5k",
"english_10k",
"english_25k",
"english_450k",
"english_commonly_misspelled",
"english_contractions",
"english_doubleletter",
"english_shakespearean",
"english_old",
"english_medical",
],
spanish: ["spanish", "spanish_1k", "spanish_10k", "spanish_650k"],
french: [
"french",
"french_1k",
"french_2k",
"french_10k",
"french_600k",
"french_bitoduc",
],
german: ["german", "german_1k", "german_10k", "german_250k"],
swiss_german: ["swiss_german", "swiss_german_1k", "swiss_german_2k"],
portuguese: [
"portuguese",
"portuguese_1k",
"portuguese_3k",
"portuguese_5k",
"portuguese_320k",
"portuguese_550k",
"portuguese_acentos_e_cedilha",
],
arabic: ["arabic", "arabic_10k"],
arabic_egypt: ["arabic_egypt", "arabic_egypt_1k"],
italian: [
"italian",
"italian_1k",
"italian_7k",
"italian_60k",
"italian_280k",
],
friulian: ["friulian"],
latin: ["latin"],
afrikaans: ["afrikaans", "afrikaans_1k", "afrikaans_10k"],
mongolian: ["mongolian", "mongolian_10k"],
korean: ["korean", "korean_1k", "korean_5k"],
khmer: ["khmer"],
marathi: ["marathi"],
chinese: [
"chinese_traditional",
"chinese_simplified",
"chinese_simplified_1k",
"chinese_simplified_5k",
"chinese_simplified_10k",
"chinese_simplified_50k",
],
russian: [
"russian",
"russian_1k",
"russian_5k",
"russian_10k",
"russian_25k",
"russian_50k",
"russian_375k",
"russian_contractions",
"russian_contractions_1k",
"russian_abbreviations",
],
polish: [
"polish",
"polish_2k",
"polish_5k",
"polish_10k",
"polish_20k",
"polish_40k",
"polish_200k",
],
czech: ["czech", "czech_1k", "czech_10k"],
slovak: ["slovak", "slovak_1k", "slovak_10k"],
ukrainian: [
"ukrainian",
"ukrainian_1k",
"ukrainian_10k",
"ukrainian_50k",
"ukrainian_endings",
],
ukrainian_latynka: [
"ukrainian_latynka",
"ukrainian_latynka_1k",
"ukrainian_latynka_10k",
"ukrainian_latynka_50k",
"ukrainian_latynka_endings",
],
lithuanian: ["lithuanian", "lithuanian_1k", "lithuanian_3k"],
indonesian: ["indonesian", "indonesian_1k", "indonesian_10k"],
kurdish_central: [
"kurdish_central",
"kurdish_central_2k",
"kurdish_central_4k",
],
greek: ["greek", "greek_1k", "greek_5k", "greek_10k", "greek_25k"],
greeklish: [
"greeklish",
"greeklish_1k",
"greeklish_5k",
"greeklish_10k",
"greeklish_25k",
],
turkish: ["turkish", "turkish_1k", "turkish_5k"],
irish: ["irish"],
galician: ["galician"],
thai: ["thai", "thai_20k"],
tamil: ["tamil", "tamil_1k", "tanglish", "tamil_old"],
kannada: ["kannada"],
telugu: ["telugu", "telugu_1k"],
slovenian: ["slovenian", "slovenian_1k", "slovenian_5k"],
croatian: ["croatian", "croatian_1k"],
dutch: ["dutch", "dutch_1k", "dutch_10k"],
filipino: ["filipino", "filipino_1k"],
danish: ["danish", "danish_1k", "danish_10k"],
hungarian: ["hungarian", "hungarian_2k"],
norwegian_bokmal: [
"norwegian_bokmal",
"norwegian_bokmal_1k",
"norwegian_bokmal_5k",
"norwegian_bokmal_10k",
"norwegian_bokmal_150k",
"norwegian_bokmal_600k",
],
norwegian_nynorsk: [
"norwegian_nynorsk",
"norwegian_nynorsk_1k",
"norwegian_nynorsk_5k",
"norwegian_nynorsk_10k",
"norwegian_nynorsk_100k",
"norwegian_nynorsk_400k",
],
hebrew: ["hebrew", "hebrew_1k", "hebrew_5k", "hebrew_10k"],
icelandic: ["icelandic_1k"],
malagasy: ["malagasy", "malagasy_1k"],
malay: ["malay", "malay_1k"],
romanian: [
"romanian",
"romanian_1k",
"romanian_5k",
"romanian_10k",
"romanian_25k",
"romanian_50k",
"romanian_100k",
"romanian_200k",
],
finnish: ["finnish", "finnish_1k", "finnish_10k"],
estonian: ["estonian", "estonian_1k", "estonian_5k", "estonian_10k"],
udmurt: ["udmurt"],
welsh: ["welsh", "welsh_1k"],
persian: [
"persian",
"persian_1k",
"persian_5k",
"persian_20k",
"persian_romanized",
],
kazakh: ["kazakh", "kazakh_1k"],
vietnamese: ["vietnamese", "vietnamese_1k", "vietnamese_5k"],
jyutping: ["jyutping"],
pinyin: ["pinyin", "pinyin_1k", "pinyin_10k"],
hausa: ["hausa", "hausa_1k"],
swedish: ["swedish", "swedish_1k", "swedish_diacritics"],
serbian: ["serbian", "serbian_latin", "serbian_10k", "serbian_latin_10k"],
georgian: ["georgian"],
yoruba: ["yoruba_1k"],
swahili: ["swahili_1k"],
maori: ["maori_1k"],
catalan: ["catalan", "catalan_1k"],
bulgarian: ["bulgarian", "bulgarian_latin"],
bosnian: ["bosnian", "bosnian_4k"],
esperanto: [
"esperanto",
"esperanto_1k",
"esperanto_10k",
"esperanto_25k",
"esperanto_36k",
"esperanto_x_sistemo",
"esperanto_x_sistemo_1k",
"esperanto_x_sistemo_10k",
"esperanto_x_sistemo_25k",
"esperanto_x_sistemo_36k",
"esperanto_h_sistemo",
"esperanto_h_sistemo_1k",
"esperanto_h_sistemo_10k",
"esperanto_h_sistemo_25k",
"esperanto_h_sistemo_36k",
],
bangla: ["bangla", "bangla_letters", "bangla_10k"],
urdu: ["urdu", "urdu_1k", "urdu_5k", "urdish"],
albanian: ["albanian", "albanian_1k"],
shona: ["shona", "shona_1k"],
armenian: [
"armenian",
"armenian_1k",
"armenian_western",
"armenian_western_1k",
],
myanmar: ["myanmar_burmese"],
japanese: [
"japanese_hiragana",
"japanese_katakana",
"japanese_romaji",
"japanese_romaji_1k",
],
hindi: ["hindi", "hindi_1k", "hinglish"],
sanskrit: ["sanskrit", "sanskrit_roman"],
gujarati: ["gujarati", "gujarati_1k"],
nepali: ["nepali", "nepali_1k", "nepali_romanized"],
santali: ["santali"],
macedonian: [
"macedonian",
"macedonian_1k",
"macedonian_10k",
"macedonian_75k",
],
uzbek: ["uzbek", "uzbek_1k", "uzbek_70k"],
belarusian: [
"belarusian",
"belarusian_1k",
"belarusian_5k",
"belarusian_10k",
"belarusian_25k",
"belarusian_50k",
"belarusian_100k",
],
belarusian_lacinka: ["belarusian_lacinka", "belarusian_lacinka_1k"],
tatar: ["tatar", "tatar_1k", "tatar_5k", "tatar_9k"],
tatar_crimean: [
"tatar_crimean",
"tatar_crimean_1k",
"tatar_crimean_5k",
"tatar_crimean_10k",
"tatar_crimean_15k",
"tatar_crimean_cyrillic",
"tatar_crimean_cyrillic_1k",
"tatar_crimean_cyrillic_5k",
"tatar_crimean_cyrillic_10k",
"tatar_crimean_cyrillic_15k",
],
azerbaijani: ["azerbaijani", "azerbaijani_1k"],
malayalam: ["malayalam"],
sinhala: ["sinhala"],
latvian: ["latvian", "latvian_1k"],
maltese: ["maltese", "maltese_1k"],
toki_pona: ["toki_pona", "toki_pona_ku_suli", "toki_pona_ku_lili"],
xhosa: ["xhosa", "xhosa_3k"],
tibetan: ["tibetan", "tibetan_1k"],
bashkir: ["bashkir"],
other: [
"lojban_gismu",
"lojban_cmavo",
"pig_latin",
"twitch_emotes",
"git",
"typing_of_the_dead",
"league_of_legends",
"docker_file",
],
amharic: ["amharic", "amharic_1k", "amharic_5k"],
oromo: ["oromo", "oromo_1k", "oromo_5k"],
wordle: ["wordle", "wordle_1k"],
kyrgyz: ["kyrgyz", "kyrgyz_1k"],
yiddish: ["yiddish"],
frisian: ["frisian", "frisian_1k"],
pashto: ["pashto"],
euskera: ["euskera"],
klingon: ["klingon", "klingon_1k"],
quenya: ["quenya"],
lorem_ipsum: ["lorem_ipsum"],
occitan: ["occitan", "occitan_1k", "occitan_2k", "occitan_5k", "occitan_10k"],
kabyle: ["kabyle", "kabyle_1k", "kabyle_2k", "kabyle_5k", "kabyle_10k"],
zulu: ["zulu"],
code: [
"code_python",
"code_python_1k",
"code_python_2k",
"code_python_5k",
"code_c",
"code_css",
"code_csharp",
"code_c++",
"code_dart",
"code_brainfck",
"code_fsharp",
"code_javascript",
"code_javascript_1k",
"code_javascript_react",
"code_jule",
"code_julia",
"code_haskell",
"code_html",
"code_nim",
"code_nix",
"code_pascal",
"code_java",
"code_kotlin",
"code_go",
"code_rockstar",
"code_rust",
"code_ruby",
"code_r",
"code_r_2k",
"code_swift",
"code_scala",
"code_bash",
"code_powershell",
"code_lua",
"code_luau",
"code_latex",
"code_typst",
"code_matlab",
"code_sql",
"code_perl",
"code_php",
"code_vim",
"code_vimscript",
"code_opencl",
"code_visual_basic",
"code_arduino",
"code_systemverilog",
"code_elixir",
"code_zig",
"code_gdscript",
"code_gdscript_2",
"code_assembly",
"code_v",
"code_ook",
"code_typescript",
"code_cobol",
"code_common_lisp",
"code_odin",
"code_fortran",
],
};
export type LanguageGroupName = keyof typeof LanguageGroups;
export const LanguageGroupNames: LanguageGroupName[] = Array.from(
Object.keys(LanguageGroups)
);
/**
* Fetches the language group for a given language.
* @param language The language code.
* @returns the language group.
*/
export function getGroupForLanguage(
language: Language
): LanguageGroupName | undefined {
return LanguageGroupNames.find((group) => group.includes(language));
}

View file

@ -3,7 +3,6 @@ import * as Notifications from "../elements/notifications";
import Config, * as UpdateConfig from "../config";
import * as AccountButton from "../elements/account-button";
import * as Misc from "../utils/misc";
import * as JSONData from "../utils/json-data";
import * as Settings from "../pages/settings";
import * as DB from "../db";
import * as TestLogic from "../test/test-logic";
@ -50,6 +49,7 @@ import * as PSA from "../elements/psa";
import defaultResultFilters from "../constants/default-result-filters";
import { getActiveFunboxesWithFunction } from "../test/funbox/list";
import { Snapshot } from "../constants/default-snapshot";
import { LanguageList } from "../constants/languages";
import * as Sentry from "../sentry";
export const gmailProvider = new GoogleAuthProvider();
@ -136,26 +136,15 @@ async function getDataAndInit(): Promise<boolean> {
ResultFilters.loadTags(snapshot.tags);
Promise.all([JSONData.getLanguageList(), getAllFunboxes()])
.then((values) => {
const [languages, funboxes] = values;
languages.forEach((language) => {
defaultResultFilters.language[language] = true;
});
funboxes.forEach((funbox) => {
defaultResultFilters.funbox[funbox.name] = true;
});
// filters = defaultResultFilters;
void ResultFilters.load();
})
.catch((e: unknown) => {
console.log(
Misc.createErrorMessage(
e,
"Something went wrong while loading the filters"
)
);
});
for (const language of LanguageList) {
defaultResultFilters.language[language] = true;
}
for (const funbox of getAllFunboxes()) {
defaultResultFilters.funbox[funbox.name] = true;
}
// filters = defaultResultFilters;
void ResultFilters.load();
if (snapshot.needsToChangeName) {
Notifications.addPSA(

View file

@ -5,6 +5,7 @@ import { subscribe } from "../observables/config-event";
import * as DB from "../db";
import Ape from "../ape";
import { tryCatch } from "@monkeytype/util/trycatch";
import { Language } from "@monkeytype/contracts/schemas/languages";
export type Quote = {
text: string;
@ -13,7 +14,7 @@ export type Quote = {
length: number;
id: number;
group: number;
language: string;
language: Language;
textSplit?: string[];
};
@ -22,7 +23,7 @@ export type QuoteWithTextSplit = Quote & {
};
type QuoteData = {
language: string;
language: Language;
quotes: {
text: string;
britishText?: string;
@ -54,7 +55,7 @@ class QuotesController {
private queueIndex = 0;
async getQuotes(
language: string,
language: Language,
quoteLengths?: number[]
): Promise<QuoteCollection> {
const normalizedLanguage = removeLanguageSize(language);
@ -164,7 +165,7 @@ class QuotesController {
return randomQuote;
}
getRandomFavoriteQuote(language: string): Quote | null {
getRandomFavoriteQuote(language: Language): Quote | null {
const snapshot = DB.getSnapshot();
if (!snapshot) {
return null;
@ -178,7 +179,7 @@ class QuotesController {
return null;
}
Object.keys(favoriteQuotes).forEach((language) => {
(Object.keys(favoriteQuotes) as Language[]).forEach((language) => {
if (removeLanguageSize(language) !== normalizedLanguage) {
return;
}
@ -210,12 +211,14 @@ class QuotesController {
const normalizedQuoteLanguage = removeLanguageSize(quoteLanguage);
const matchedLanguage = Object.keys(favoriteQuotes).find((language) => {
if (normalizedQuoteLanguage !== removeLanguageSize(language)) {
return false;
const matchedLanguage = (Object.keys(favoriteQuotes) as Language[]).find(
(language) => {
if (normalizedQuoteLanguage !== removeLanguageSize(language)) {
return false;
}
return (favoriteQuotes[language] ?? []).includes(id.toString());
}
return (favoriteQuotes[language] ?? []).includes(id.toString());
});
);
return matchedLanguage !== undefined;
}

View file

@ -30,6 +30,7 @@ import {
import { getDefaultConfig } from "./constants/default-config";
import { FunboxMetadata } from "../../../packages/funbox/src/types";
import { getFirstDayOfTheWeek } from "./utils/date-and-time";
import { Language } from "@monkeytype/contracts/schemas/languages";
let dbSnapshot: Snapshot | undefined;
const firstDayOfTheWeek = getFirstDayOfTheWeek();
@ -659,7 +660,7 @@ export async function saveLocalPB<M extends Mode>(
mode2: Mode2<M>,
punctuation: boolean,
numbers: boolean,
language: string,
language: Language,
difficulty: Difficulty,
lazyMode: boolean,
wpm: number,
@ -788,7 +789,7 @@ export async function saveLocalTagPB<M extends Mode>(
mode2: Mode2<M>,
punctuation: boolean,
numbers: boolean,
language: string,
language: Language,
difficulty: Difficulty,
lazyMode: boolean,
wpm: number,
@ -893,7 +894,7 @@ export async function saveLocalTagPB<M extends Mode>(
export async function updateLbMemory<M extends Mode>(
mode: M,
mode2: Mode2<M>,
language: string,
language: Language,
rank: number,
api = false
): Promise<void> {

View file

@ -1,6 +1,5 @@
import * as Misc from "../../utils/misc";
import * as Strings from "../../utils/strings";
import * as JSONData from "../../utils/json-data";
import * as DB from "../../db";
import Config from "../../config";
import * as Notifications from "../notifications";
@ -18,7 +17,7 @@ import { LocalStorageWithSchema } from "../../utils/local-storage-with-schema";
import defaultResultFilters from "../../constants/default-result-filters";
import { getAllFunboxes } from "@monkeytype/funbox";
import { SnapshotUserTag } from "../../constants/default-snapshot";
import { tryCatch } from "@monkeytype/util/trycatch";
import { LanguageList } from "../../constants/languages";
export function mergeWithDefaultFilters(
filters: Partial<ResultFilters>
@ -746,107 +745,68 @@ export async function appendButtons(
): Promise<void> {
selectChangeCallbackFn = selectChangeCallback;
const { data: languageList, error } = await tryCatch(
JSONData.getLanguageList()
);
if (error) {
console.error(
Misc.createErrorMessage(error, "Failed to append language buttons")
);
}
if (languageList) {
let html = "";
html +=
"<select class='languageSelect' group='language' placeholder='select a language' multiple>";
html += "<option value='all'>all</option>";
for (const language of languageList) {
html += `<option value="${language}" filter="${language}">${Strings.getLanguageDisplayString(
language
)}</option>`;
}
html += "</select>";
const el = document.querySelector(
".pageAccount .content .filterButtons .buttonsAndTitle.languages .select"
);
if (el) {
el.innerHTML = html;
groupSelects["language"] = new SlimSelect({
select: el.querySelector(".languageSelect") as HTMLSelectElement,
settings: {
showSearch: true,
placeholderText: "select a language",
allowDeselect: true,
closeOnSelect: false,
},
events: {
beforeChange: (
selectedOptions,
oldSelectedOptions
): void | boolean => {
return selectBeforeChangeFn(
"language",
selectedOptions,
oldSelectedOptions
);
},
beforeOpen: (): void => {
adjustScrollposition("language");
},
},
});
}
}
let html = "";
html +=
"<select class='funboxSelect' group='funbox' placeholder='select a funbox' multiple>";
html += "<option value='all'>all</option>";
html += "<option value='none'>no funbox</option>";
for (const funbox of getAllFunboxes()) {
html += `<option value="${funbox.name}" filter="${
funbox.name
}">${funbox.name.replace(/_/g, " ")}</option>`;
}
html += "</select>";
const el = document.querySelector(
".pageAccount .content .filterButtons .buttonsAndTitle.funbox .select"
);
if (el) {
el.innerHTML = html;
groupSelects["funbox"] = new SlimSelect({
select: el.querySelector(".funboxSelect") as HTMLSelectElement,
settings: {
showSearch: true,
placeholderText: "select a funbox",
allowDeselect: true,
closeOnSelect: false,
groupSelects["language"] = new SlimSelect({
select:
".pageAccount .content .filterButtons .buttonsAndTitle.languages .select .languageSelect",
data: [
{ value: "all", text: "all" },
...LanguageList.map((language) => ({
value: language,
text: Strings.getLanguageDisplayString(language),
filter: language,
})),
],
settings: {
showSearch: true,
placeholderText: "select a language",
allowDeselect: true,
closeOnSelect: false,
},
events: {
beforeChange: (selectedOptions, oldSelectedOptions): void | boolean => {
return selectBeforeChangeFn(
"language",
selectedOptions,
oldSelectedOptions
);
},
events: {
beforeChange: (selectedOptions, oldSelectedOptions): void | boolean => {
return selectBeforeChangeFn(
"funbox",
selectedOptions,
oldSelectedOptions
);
},
beforeOpen: (): void => {
adjustScrollposition("funbox");
},
beforeOpen: (): void => {
adjustScrollposition("language");
},
});
}
},
});
groupSelects["funbox"] = new SlimSelect({
select:
".pageAccount .content .filterButtons .buttonsAndTitle.funbox .select .funboxSelect",
data: [
{ value: "all", text: "all" },
{ value: "none", text: "no funbox" },
...getAllFunboxes().map((funbox) => ({
value: funbox.name,
text: funbox.name.replace(/_/g, " "),
filter: funbox.name,
})),
],
settings: {
showSearch: true,
placeholderText: "select a funbox",
allowDeselect: true,
closeOnSelect: false,
},
events: {
beforeChange: (selectedOptions, oldSelectedOptions): void | boolean => {
return selectBeforeChangeFn(
"funbox",
selectedOptions,
oldSelectedOptions
);
},
beforeOpen: (): void => {
adjustScrollposition("funbox");
},
},
});
const snapshot = DB.getSnapshot();

View file

@ -1,3 +1,4 @@
import { Language } from "@monkeytype/contracts/schemas/languages";
import Ape from "../ape";
import { Quote } from "../controllers/quotes-controller";
import * as DB from "../db";
@ -12,7 +13,7 @@ type QuoteStats = {
ratings?: number;
totalRating?: number;
quoteId?: number;
language?: string;
language?: Language;
};
let quoteStats: QuoteStats | null | Record<string, never> = null;

View file

@ -263,10 +263,10 @@ export async function show(showOptions?: ShowOptions): Promise<void> {
$("#quoteSearchModal .toggleFavorites").removeClass("hidden");
}
const quoteMod = DB.getSnapshot()?.quoteMod;
const isQuoteMod =
DB.getSnapshot()?.quoteMod !== undefined &&
(DB.getSnapshot()?.quoteMod === true ||
DB.getSnapshot()?.quoteMod !== "");
quoteMod !== undefined &&
(quoteMod === true || (quoteMod as string) !== "");
if (isQuoteMod) {
$("#quoteSearchModal .goToQuoteApprove").removeClass("hidden");
@ -348,7 +348,7 @@ const searchForQuotes = debounce(250, (): void => {
async function toggleFavoriteForQuote(quoteId: string): Promise<void> {
const quoteLang = Config.language;
if (quoteLang === "" || quoteId === "") {
if (quoteLang === undefined || quoteId === "") {
Notifications.add("Could not get quote stats!", -1);
return;
}

View file

@ -3,20 +3,21 @@ import * as Loader from "../elements/loader";
import * as Notifications from "../elements/notifications";
import * as CaptchaController from "../controllers/captcha-controller";
import * as Strings from "../utils/strings";
import * as JSONData from "../utils/json-data";
import Config from "../config";
import SlimSelect from "slim-select";
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
import { CharacterCounter } from "../elements/character-counter";
import { Language } from "@monkeytype/contracts/schemas/languages";
import { LanguageGroupNames } from "../constants/languages";
let dropdownReady = false;
async function initDropdown(): Promise<void> {
if (dropdownReady) return;
const languageGroups = await JSONData.getLanguageGroups();
for (const group of languageGroups) {
if (group.name === "swiss_german") continue;
for (const group of LanguageGroupNames) {
if (group === "swiss_german") continue;
$("#quoteSubmitModal .newQuoteLanguage").append(
`<option value="${group.name}">${group.name.replace(/_/g, " ")}</option>`
`<option value="${group}">${group.replace(/_/g, " ")}</option>`
);
}
dropdownReady = true;
@ -27,7 +28,7 @@ let select: SlimSelect | undefined = undefined;
async function submitQuote(): Promise<void> {
const text = $("#quoteSubmitModal .newQuoteText").val() as string;
const source = $("#quoteSubmitModal .newQuoteSource").val() as string;
const language = $("#quoteSubmitModal .newQuoteLanguage").val() as string;
const language = $("#quoteSubmitModal .newQuoteLanguage").val() as Language;
const captcha = CaptchaController.getResponse("submitQuote");
if (!text || !source || !language) {

View file

@ -9,6 +9,8 @@ import AnimatedModal, {
} from "../utils/animated-modal";
import { LayoutsList } from "../constants/layouts";
import { tryCatch } from "@monkeytype/util/trycatch";
import { LanguageList } from "../constants/languages";
import { Language } from "@monkeytype/contracts/schemas/languages";
type FilterPreset = {
display: string;
@ -102,20 +104,6 @@ async function initSelectOptions(): Promise<void> {
$("#wordFilterModal .layoutInput").empty();
$("wordFilterModal .presetInput").empty();
const { data: LanguageList, error } = await tryCatch(
JSONData.getLanguageList()
);
if (error) {
console.error(
Misc.createErrorMessage(
error,
"Failed to initialise word filter popup language list"
)
);
return;
}
LanguageList.forEach((language) => {
const prettyLang = language.replace(/_/gi, " ");
$("#wordFilterModal .languageInput").append(`
@ -175,7 +163,7 @@ function hide(hideOptions?: HideOptions<OutgoingData>): void {
});
}
async function filter(language: string): Promise<string[]> {
async function filter(language: Language): Promise<string[]> {
let filterin = $("#wordFilterModal .wordIncludeInput").val() as string;
filterin = Misc.escapeRegExp(filterin?.trim());
filterin = filterin.replace(/\s+/gi, "|");
@ -226,7 +214,7 @@ async function filter(language: string): Promise<string[]> {
}
async function apply(set: boolean): Promise<void> {
const language = $("#wordFilterModal .languageInput").val() as string;
const language = $("#wordFilterModal .languageInput").val() as Language;
const filteredWords = await filter(language);
if (filteredWords.length === 0) {

View file

@ -373,13 +373,11 @@ async function fillContent(): Promise<void> {
}
}
let langFilter = ResultFilters.getFilter(
"language",
result.language ?? "english"
);
let langFilter = ResultFilters.getFilter("language", result.language);
if (
result.language === "english_expanded" &&
//legacy value for english_1k
(result.language as string) === "english_expanded" &&
ResultFilters.getFilter("language", "english_1k")
) {
langFilter = true;

View file

@ -42,6 +42,10 @@ import { UTCDateMini } from "@date-fns/utc";
import * as ConfigEvent from "../observables/config-event";
import * as ActivePage from "../states/active-page";
import { PaginationQuery } from "@monkeytype/contracts/leaderboards";
import {
Language,
LanguageSchema,
} from "@monkeytype/contracts/schemas/languages";
const LeaderboardTypeSchema = z.enum(["allTime", "weekly", "daily"]);
type LeaderboardType = z.infer<typeof LeaderboardTypeSchema>;
@ -71,7 +75,7 @@ type DailyState = {
mode2: "15" | "60";
yesterday: boolean;
minWpm: number;
language: string;
language: Language;
data: LeaderboardEntry[] | null;
count: number;
userData: LeaderboardEntry | null;
@ -108,7 +112,7 @@ const state = {
const SelectorSchema = z.object({
type: LeaderboardTypeSchema,
mode2: z.enum(["15", "60"]).optional(),
language: z.string().optional(),
language: LanguageSchema.optional(),
yesterday: z.boolean().optional(),
lastWeek: z.boolean().optional(),
});
@ -1255,7 +1259,7 @@ $(".page.pageLeaderboards .buttonGroup.secondary").on(
"button",
function () {
const mode = $(this).attr("data-mode") as "15" | "60" | undefined;
const language = $(this).data("language") as string;
const language = $(this).data("language") as Language;
if (
mode !== undefined &&
(state.type === "allTime" || state.type === "daily")

View file

@ -35,6 +35,8 @@ import { tryCatch } from "@monkeytype/util/trycatch";
import { Theme, ThemesList } from "../constants/themes";
import { areSortedArraysEqual, areUnsortedArraysEqual } from "../utils/arrays";
import { LayoutName } from "@monkeytype/contracts/schemas/layouts";
import { LanguageGroupNames, LanguageGroups } from "../constants/languages";
import { Language } from "@monkeytype/contracts/schemas/languages";
let settingsInitialized = false;
@ -438,25 +440,9 @@ async function fillSettingsPage(): Promise<void> {
return;
}
// Language Selection Combobox
const { data: languageGroups, error: getLanguageGroupsError } =
await tryCatch(JSONData.getLanguageGroups());
if (getLanguageGroupsError) {
console.error(
Misc.createErrorMessage(
getLanguageGroupsError,
"Failed to initialize settings language picker"
)
);
}
new SlimSelect({
select: ".pageSettings .section[data-config-name='language'] select",
data: getLanguageDropdownData(
languageGroups ?? [],
(language) => language === Config.language
),
data: getLanguageDropdownData((language) => language === Config.language),
settings: {
searchPlaceholder: "search",
},
@ -612,12 +598,12 @@ async function fillSettingsPage(): Promise<void> {
customPolyglotSelect = new SlimSelect({
select: ".pageSettings .section[data-config-name='customPolyglot'] select",
data: getLanguageDropdownData(languageGroups ?? [], (language) =>
data: getLanguageDropdownData((language) =>
Config.customPolyglot.includes(language)
),
events: {
afterChange: (newVal): void => {
const customPolyglot = newVal.map((it) => it.value);
const customPolyglot = newVal.map((it) => it.value) as Language[];
//checking equal without order, because customPolyglot is not ordered
if (!areUnsortedArraysEqual(customPolyglot, Config.customPolyglot)) {
void UpdateConfig.setCustomPolyglot(customPolyglot);
@ -1309,14 +1295,13 @@ export function setEventDisabled(value: boolean): void {
}
function getLanguageDropdownData(
languageGroups: JSONData.LanguageGroup[],
isActive: (val: string) => boolean
isActive: (val: Language) => boolean
): DataArrayPartial {
return languageGroups.map(
return LanguageGroupNames.map(
(group) =>
({
label: group.name,
options: group.languages.map((language) => ({
label: group,
options: LanguageGroups[group]?.map((language) => ({
text: Strings.getLanguageDisplayString(language),
value: language,
selected: isActive(language),

View file

@ -27,7 +27,7 @@ import {
KeymapLayout,
Layout,
} from "@monkeytype/contracts/schemas/configs";
import { Language } from "@monkeytype/contracts/schemas/languages";
export type FunboxFunctions = {
getWord?: (wordset?: Wordset, wordIndex?: number) => string;
punctuateWord?: (word: string) => string;
@ -38,7 +38,7 @@ export type FunboxFunctions = {
clearGlobal?: () => void;
rememberSettings?: () => void;
toggleScript?: (params: string[]) => void;
pullSection?: (language?: string) => Promise<Section | false>;
pullSection?: (language?: Language) => Promise<Section | false>;
handleSpace?: () => void;
handleChar?: (char: string) => string;
isCharCorrect?: (char: string, originalChar: string) => boolean;
@ -509,7 +509,7 @@ const list: Partial<Record<FunboxName, FunboxFunctions>> = {
},
},
wikipedia: {
async pullSection(lang?: string): Promise<JSONData.Section | false> {
async pullSection(lang?: Language): Promise<JSONData.Section | false> {
return getSection((lang ?? "") || "english");
},
},

View file

@ -40,13 +40,14 @@ import { CompletedEvent } from "@monkeytype/contracts/schemas/results";
import { getActiveFunboxes, isFunboxActiveWithProperty } from "./funbox/list";
import { getFunbox } from "@monkeytype/funbox";
import { SnapshotUserTag } from "../constants/default-snapshot";
import { Language } from "@monkeytype/contracts/schemas/languages";
let result: CompletedEvent;
let maxChartVal: number;
let useUnsmoothedRaw = false;
let quoteLang = "";
let quoteLang: Language | undefined;
let quoteId = "";
export function toggleUnsmoothedRaw(): void {
@ -816,7 +817,7 @@ function updateQuoteFavorite(randomQuote: Quote | null): void {
return;
}
quoteLang = Config.mode === "quote" ? randomQuote.language : "";
quoteLang = Config.mode === "quote" ? randomQuote.language : undefined;
quoteId = Config.mode === "quote" ? randomQuote.id.toString() : "";
const userFav = QuotesController.isQuoteFavorite(randomQuote);
@ -1041,7 +1042,7 @@ export function updateTagsAfterEdit(
}
$(".pageTest #favoriteQuoteButton").on("click", async () => {
if (quoteLang === "" || quoteId === "") {
if (quoteLang === undefined || quoteId === "") {
Notifications.add("Could not get quote stats!", -1);
return;
}

View file

@ -4,10 +4,11 @@ import * as Strings from "../utils/strings";
import * as JSONData from "../utils/json-data";
import { z } from "zod";
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
import { tryCatch } from "@monkeytype/util/trycatch";
import { getGroupForLanguage, LanguageGroupName } from "../constants/languages";
import { Language } from "@monkeytype/contracts/schemas/languages";
export async function getTLD(
languageGroup: JSONData.LanguageGroup
languageGroup: LanguageGroupName
): Promise<
| "en"
| "es"
@ -65,7 +66,7 @@ export async function getTLD(
| "eu"
> {
// language group to tld
switch (languageGroup.name) {
switch (languageGroup) {
case "english":
return "en";
@ -256,23 +257,17 @@ const SectionSchema = z.object({
}),
});
export async function getSection(language: string): Promise<JSONData.Section> {
export async function getSection(
language: Language
): Promise<JSONData.Section> {
// console.log("Getting section");
Loader.show();
// get TLD for wikipedia according to language group
let urlTLD = "en";
const { data: currentLanguageGroup, error } = await tryCatch(
JSONData.getCurrentGroup(language)
);
if (error) {
console.error(
Misc.createErrorMessage(error, "Failed to find current language group")
);
}
if (currentLanguageGroup !== null && currentLanguageGroup !== undefined) {
const currentLanguageGroup = getGroupForLanguage(language);
if (currentLanguageGroup !== undefined) {
urlTLD = await getTLD(currentLanguageGroup);
}
@ -293,7 +288,6 @@ export async function getSection(language: string): Promise<JSONData.Section> {
Loader.hide();
rej(randomPostReq.status);
}
const sectionURL = `https://${urlTLD}.wikipedia.org/w/api.php?action=query&format=json&pageids=${pageid}&prop=extracts&exintro=true&origin=*`;
const sectionReq = new XMLHttpRequest();

View file

@ -1,4 +1,5 @@
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
import { Language } from "@monkeytype/contracts/schemas/languages";
import { Accents } from "../test/lazy-mode";
/**
@ -91,33 +92,8 @@ export async function getLayout(layoutName: string): Promise<Layout> {
return await cachedFetchJson<Layout>(`/layouts/${layoutName}.json`);
}
/**
* Fetches the list of languages from the server.
* @returns A promise that resolves to the list of languages.
*/
export async function getLanguageList(): Promise<string[]> {
const languageList = await cachedFetchJson<string[]>("/languages/_list.json");
return languageList;
}
export type LanguageGroup = {
name: string;
languages: string[];
};
/**
* Fetches the list of language groups from the server.
* @returns A promise that resolves to the list of language groups.
*/
export async function getLanguageGroups(): Promise<LanguageGroup[]> {
const languageGroupList = await cachedFetchJson<LanguageGroup[]>(
"/languages/_groups.json"
);
return languageGroupList;
}
export type LanguageObject = {
name: string;
name: Language;
rightToLeft: boolean;
noLazyMode?: boolean;
ligatures?: boolean;
@ -135,7 +111,8 @@ let currentLanguage: LanguageObject;
* @param lang The language code.
* @returns A promise that resolves to the language object.
*/
export async function getLanguage(lang: string): Promise<LanguageObject> {
export async function getLanguage(lang: Language): Promise<LanguageObject> {
// try {
if (currentLanguage === undefined || currentLanguage.name !== lang) {
currentLanguage = await cachedFetchJson<LanguageObject>(
`/languages/${lang}.json`
@ -145,7 +122,7 @@ export async function getLanguage(lang: string): Promise<LanguageObject> {
}
export async function checkIfLanguageSupportsZipf(
language: string
language: Language
): Promise<"yes" | "no" | "unknown"> {
const lang = await getLanguage(language);
if (lang.orderedByFrequency === true) return "yes";
@ -159,31 +136,11 @@ export async function checkIfLanguageSupportsZipf(
* @returns A promise that resolves to the current language object.
*/
export async function getCurrentLanguage(
languageName: string
languageName: Language
): Promise<LanguageObject> {
return await getLanguage(languageName);
}
/**
* Fetches the language group for a given language.
* @param language The language code.
* @returns A promise that resolves to the language group.
*/
export async function getCurrentGroup(
language: string
): Promise<LanguageGroup | undefined> {
let retgroup: LanguageGroup | undefined;
const groups = await getLanguageGroups();
groups.forEach((group) => {
if (retgroup === undefined) {
if (group.languages.includes(language)) {
retgroup = group;
}
}
});
return retgroup;
}
export class Section {
public title: string;
public author: string;

View file

@ -1,3 +1,5 @@
import { Language } from "@monkeytype/contracts/schemas/languages";
/**
* Removes accents from a string.
* https://ricardometring.com/javascript-replace-special-characters
@ -101,7 +103,7 @@ export function splitByAndKeep(text: string, delimiters: string[]): string[] {
* @returns A display string for the language.
*/
export function getLanguageDisplayString(
language: string,
language: Language,
noSizeString = false
): string {
let out = "";
@ -118,8 +120,8 @@ export function getLanguageDisplayString(
* @param language The language string.
* @returns The language string with the size indicator removed.
*/
export function removeLanguageSize(language: string): string {
return language.replace(/_\d*k$/g, "");
export function removeLanguageSize(language: Language): Language {
return language.replace(/_\d*k$/g, "") as Language;
}
/**

View file

@ -29,6 +29,7 @@ import {
import { z } from "zod";
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
import { tryCatchSync } from "@monkeytype/util/trycatch";
import { Language } from "@monkeytype/contracts/schemas/languages";
export async function linkDiscord(hashOverride: string): Promise<void> {
if (!hashOverride) return;
@ -239,7 +240,7 @@ export function loadTestSettingsFromUrl(getOverride?: string): void {
}
if (de[5] !== null) {
UpdateConfig.setLanguage(de[5], true);
UpdateConfig.setLanguage(de[5] as Language, true);
applied["language"] = de[5];
}

View file

@ -1,682 +0,0 @@
[
{
"name": "english",
"languages": [
"english",
"english_1k",
"english_5k",
"english_10k",
"english_25k",
"english_450k",
"english_commonly_misspelled",
"english_contractions",
"english_doubleletter",
"english_shakespearean",
"english_old",
"english_medical"
]
},
{
"name": "spanish",
"languages": ["spanish", "spanish_1k", "spanish_10k", "spanish_650k"]
},
{
"name": "french",
"languages": [
"french",
"french_1k",
"french_2k",
"french_10k",
"french_600k",
"french_bitoduc"
]
},
{
"name": "german",
"languages": ["german", "german_1k", "german_10k", "german_250k"]
},
{
"name": "swiss_german",
"languages": ["swiss_german", "swiss_german_1k", "swiss_german_2k"]
},
{
"name": "portuguese",
"languages": [
"portuguese",
"portuguese_1k",
"portuguese_3k",
"portuguese_5k",
"portuguese_320k",
"portuguese_550k",
"portuguese_acentos_e_cedilha"
]
},
{
"name": "arabic",
"languages": ["arabic", "arabic_10k"]
},
{
"name": "arabic_egypt",
"languages": ["arabic_egypt", "arabic_egypt_1k"]
},
{
"name": "italian",
"languages": [
"italian",
"italian_1k",
"italian_7k",
"italian_60k",
"italian_280k"
]
},
{
"name": "friulian",
"languages": ["friulian"]
},
{
"name": "latin",
"languages": ["latin"]
},
{
"name": "afrikaans",
"languages": ["afrikaans", "afrikaans_1k", "afrikaans_10k"]
},
{
"name": "mongolian",
"languages": ["mongolian", "mongolian_10k"]
},
{
"name": "korean",
"languages": ["korean", "korean_1k", "korean_5k"]
},
{
"name": "khmer",
"languages": ["khmer"]
},
{
"name": "marathi",
"languages": ["marathi"]
},
{
"name": "chinese",
"languages": [
"chinese_traditional",
"chinese_simplified",
"chinese_simplified_1k",
"chinese_simplified_5k",
"chinese_simplified_10k",
"chinese_simplified_50k"
]
},
{
"name": "russian",
"languages": [
"russian",
"russian_1k",
"russian_5k",
"russian_10k",
"russian_25k",
"russian_50k",
"russian_375k",
"russian_contractions",
"russian_contractions_1k",
"russian_abbreviations"
]
},
{
"name": "polish",
"languages": [
"polish",
"polish_2k",
"polish_5k",
"polish_10k",
"polish_20k",
"polish_40k",
"polish_200k"
]
},
{
"name": "czech",
"languages": ["czech", "czech_1k", "czech_10k"]
},
{
"name": "slovak",
"languages": ["slovak", "slovak_1k", "slovak_10k"]
},
{
"name": "ukrainian",
"languages": [
"ukrainian",
"ukrainian_1k",
"ukrainian_10k",
"ukrainian_50k",
"ukrainian_endings"
]
},
{
"name": "ukrainian_latynka",
"languages": [
"ukrainian_latynka",
"ukrainian_latynka_1k",
"ukrainian_latynka_10k",
"ukrainian_latynka_50k",
"ukrainian_latynka_endings"
]
},
{
"name": "lithuanian",
"languages": ["lithuanian", "lithuanian_1k", "lithuanian_3k"]
},
{
"name": "indonesian",
"languages": ["indonesian", "indonesian_1k", "indonesian_10k"]
},
{
"name": "kurdish_central",
"languages": ["kurdish_central", "kurdish_central_2k", "kurdish_central_4k"]
},
{
"name": "greek",
"languages": ["greek", "greek_1k", "greek_5k", "greek_10k", "greek_25k"]
},
{
"name": "greeklish",
"languages": [
"greeklish",
"greeklish_1k",
"greeklish_5k",
"greeklish_10k",
"greeklish_25k"
]
},
{
"name": "turkish",
"languages": ["turkish", "turkish_1k", "turkish_5k"]
},
{
"name": "irish",
"languages": ["irish"]
},
{
"name": "galician",
"languages": ["galician"]
},
{
"name": "thai",
"languages": ["thai", "thai_20k"]
},
{
"name": "tamil",
"languages": ["tamil", "tamil_1k", "tanglish", "tamil_old"]
},
{
"name": "kannada",
"languages": ["kannada"]
},
{
"name": "telugu",
"languages": ["telugu", "telugu_1k"]
},
{
"name": "slovenian",
"languages": ["slovenian", "slovenian_1k", "slovenian_5k"]
},
{
"name": "croatian",
"languages": ["croatian", "croatian_1k"]
},
{
"name": "dutch",
"languages": ["dutch", "dutch_1k", "dutch_10k"]
},
{
"name": "filipino",
"languages": ["filipino", "filipino_1k"]
},
{
"name": "danish",
"languages": ["danish", "danish_1k", "danish_10k"]
},
{
"name": "hungarian",
"languages": ["hungarian", "hungarian_2k"]
},
{
"name": "norwegian_bokmal",
"languages": [
"norwegian_bokmal",
"norwegian_bokmal_1k",
"norwegian_bokmal_5k",
"norwegian_bokmal_10k",
"norwegian_bokmal_150k",
"norwegian_bokmal_600k"
]
},
{
"name": "norwegian_nynorsk",
"languages": [
"norwegian_nynorsk",
"norwegian_nynorsk_1k",
"norwegian_nynorsk_5k",
"norwegian_nynorsk_10k",
"norwegian_nynorsk_100k",
"norwegian_nynorsk_400k"
]
},
{
"name": "hebrew",
"languages": ["hebrew", "hebrew_1k", "hebrew_5k", "hebrew_10k"]
},
{
"name": "icelandic",
"languages": ["icelandic_1k"]
},
{
"name": "malagasy",
"languages": ["malagasy", "malagasy_1k"]
},
{
"name": "malay",
"languages": ["malay", "malay_1k"]
},
{
"name": "romanian",
"languages": [
"romanian",
"romanian_1k",
"romanian_5k",
"romanian_10k",
"romanian_25k",
"romanian_50k",
"romanian_100k",
"romanian_200k"
]
},
{
"name": "finnish",
"languages": ["finnish", "finnish_1k", "finnish_10k"]
},
{
"name": "estonian",
"languages": ["estonian", "estonian_1k", "estonian_5k", "estonian_10k"]
},
{
"name": "udmurt",
"languages": ["udmurt"]
},
{
"name": "welsh",
"languages": ["welsh", "welsh_1k"]
},
{
"name": "persian",
"languages": [
"persian",
"persian_1k",
"persian_5k",
"persian_20k",
"persian_romanized"
]
},
{
"name": "kazakh",
"languages": ["kazakh", "kazakh_1k"]
},
{
"name": "vietnamese",
"languages": ["vietnamese", "vietnamese_1k", "vietnamese_5k"]
},
{
"name": "jyutping",
"languages": ["jyutping"]
},
{
"name": "pinyin",
"languages": ["pinyin", "pinyin_1k", "pinyin_10k"]
},
{
"name": "hausa",
"languages": ["hausa", "hausa_1k"]
},
{
"name": "swedish",
"languages": ["swedish", "swedish_1k", "swedish_diacritics"]
},
{
"name": "serbian",
"languages": [
"serbian",
"serbian_latin",
"serbian_10k",
"serbian_latin_10k"
]
},
{
"name": "georgian",
"languages": ["georgian"]
},
{
"name": "yoruba",
"languages": ["yoruba_1k"]
},
{
"name": "swahili",
"languages": ["swahili_1k"]
},
{
"name": "maori",
"languages": ["maori_1k"]
},
{
"name": "catalan",
"languages": ["catalan", "catalan_1k"]
},
{
"name": "bulgarian",
"languages": ["bulgarian", "bulgarian_latin"]
},
{
"name": "bosnian",
"languages": ["bosnian", "bosnian_4k"]
},
{
"name": "esperanto",
"languages": [
"esperanto",
"esperanto_1k",
"esperanto_10k",
"esperanto_25k",
"esperanto_36k",
"esperanto_x_sistemo",
"esperanto_x_sistemo_1k",
"esperanto_x_sistemo_10k",
"esperanto_x_sistemo_25k",
"esperanto_x_sistemo_36k",
"esperanto_h_sistemo",
"esperanto_h_sistemo_1k",
"esperanto_h_sistemo_10k",
"esperanto_h_sistemo_25k",
"esperanto_h_sistemo_36k"
]
},
{
"name": "bangla",
"languages": ["bangla", "bangla_letters", "bangla_10k"]
},
{
"name": "urdu",
"languages": ["urdu", "urdu_1k", "urdu_5k", "urdish"]
},
{
"name": "albanian",
"languages": ["albanian", "albanian_1k"]
},
{
"name": "shona",
"languages": ["shona", "shona_1k"]
},
{
"name": "armenian",
"languages": [
"armenian",
"armenian_1k",
"armenian_western",
"armenian_western_1k"
]
},
{
"name": "myanmar",
"languages": ["myanmar_burmese"]
},
{
"name": "japanese",
"languages": [
"japanese_hiragana",
"japanese_katakana",
"japanese_romaji",
"japanese_romaji_1k"
]
},
{
"name": "hindi",
"languages": ["hindi", "hindi_1k", "hinglish"]
},
{
"name": "sanskrit",
"languages": ["sanskrit", "sanskrit_roman"]
},
{
"name": "gujarati",
"languages": ["gujarati", "gujarati_1k"]
},
{
"name": "nepali",
"languages": ["nepali", "nepali_1k", "nepali_romanized"]
},
{
"name": "santali",
"languages": ["santali"]
},
{
"name": "macedonian",
"languages": [
"macedonian",
"macedonian_1k",
"macedonian_10k",
"macedonian_75k"
]
},
{
"name": "uzbek",
"languages": ["uzbek", "uzbek_1k", "uzbek_70k"]
},
{
"name": "belarusian",
"languages": [
"belarusian",
"belarusian_1k",
"belarusian_5k",
"belarusian_10k",
"belarusian_25k",
"belarusian_50k",
"belarusian_100k"
]
},
{
"name": "belarusian_lacinka",
"languages": ["belarusian_lacinka", "belarusian_lacinka_1k"]
},
{
"name": "tatar",
"languages": ["tatar", "tatar_1k", "tatar_5k", "tatar_9k"]
},
{
"name": "tatar_crimean",
"languages": [
"tatar_crimean",
"tatar_crimean_1k",
"tatar_crimean_5k",
"tatar_crimean_10k",
"tatar_crimean_15k",
"tatar_crimean_cyrillic",
"tatar_crimean_cyrillic_1k",
"tatar_crimean_cyrillic_5k",
"tatar_crimean_cyrillic_10k",
"tatar_crimean_cyrillic_15k"
]
},
{
"name": "azerbaijani",
"languages": ["azerbaijani", "azerbaijani_1k"]
},
{
"name": "malayalam",
"languages": ["malayalam"]
},
{
"name": "sinhala",
"languages": ["sinhala"]
},
{
"name": "latvian",
"languages": ["latvian", "latvian_1k"]
},
{
"name": "maltese",
"languages": ["maltese", "maltese_1k"]
},
{
"name": "toki_pona",
"languages": ["toki_pona", "toki_pona_ku_suli", "toki_pona_ku_lili"]
},
{
"name": "xhosa",
"languages": ["xhosa", "xhosa_3k"]
},
{
"name": "tibetan",
"languages": ["tibetan", "tibetan_1k"]
},
{
"name": "bashkir",
"languages": ["bashkir"]
},
{
"name": "other",
"languages": [
"lojban_gismu",
"lojban_cmavo",
"pig_latin",
"twitch_emotes",
"git",
"typing_of_the_dead",
"league_of_legends",
"docker_file"
]
},
{
"name": "amharic",
"languages": ["amharic", "amharic_1k", "amharic_5k"]
},
{
"name": "oromo",
"languages": ["oromo", "oromo_1k", "oromo_5k"]
},
{
"name": "wordle",
"languages": ["wordle", "wordle_1k"]
},
{
"name": "kyrgyz",
"languages": ["kyrgyz", "kyrgyz_1k"]
},
{
"name": "yiddish",
"languages": ["yiddish"]
},
{
"name": "frisian",
"languages": ["frisian", "frisian_1k"]
},
{
"name": "pashto",
"languages": ["pashto"]
},
{
"name": "euskera",
"languages": ["euskera"]
},
{
"name": "klingon",
"languages": ["klingon", "klingon_1k"]
},
{
"name": "quenya",
"languages": ["quenya"]
},
{
"name": "lorem_ipsum",
"languages": ["lorem_ipsum"]
},
{
"name": "occitan",
"languages": [
"occitan",
"occitan_1k",
"occitan_2k",
"occitan_5k",
"occitan_10k"
]
},
{
"name": "kabyle",
"languages": ["kabyle", "kabyle_1k", "kabyle_2k", "kabyle_5k", "kabyle_10k"]
},
{
"name": "zulu",
"languages": ["zulu"]
},
{
"name": "code",
"languages": [
"code_python",
"code_python_1k",
"code_python_2k",
"code_python_5k",
"code_c",
"code_csharp",
"code_c++",
"code_dart",
"code_brainfck",
"code_fsharp",
"code_javascript",
"code_javascript_1k",
"code_javascript_react",
"code_jule",
"code_julia",
"code_haskell",
"code_html",
"code_nim",
"code_nix",
"code_pascal",
"code_java",
"code_kotlin",
"code_go",
"code_rockstar",
"code_rust",
"code_ruby",
"code_r",
"code_r_2k",
"code_swift",
"code_scala",
"code_bash",
"code_powershell",
"code_lua",
"code_luau",
"code_latex",
"code_typst",
"code_matlab",
"code_sql",
"code_perl",
"code_php",
"code_vim",
"code_vimscript",
"code_opencl",
"code_visual_basic",
"code_arduino",
"code_systemverilog",
"code_elixir",
"code_zig",
"code_gdscript",
"code_gdscript_2",
"code_assembly",
"code_v",
"code_ook",
"code_typescript",
"code_cobol",
"code_common_lisp",
"code_odin",
"code_fortran"
]
}
]

View file

@ -1,402 +0,0 @@
[
"english"
,"english_1k"
,"english_5k"
,"english_10k"
,"english_25k"
,"english_450k"
,"english_commonly_misspelled"
,"english_contractions"
,"english_doubleletter"
,"english_shakespearean"
,"english_old"
,"english_medical"
,"spanish"
,"spanish_1k"
,"spanish_10k"
,"spanish_650k"
,"french"
,"french_1k"
,"french_2k"
,"french_10k"
,"french_600k"
,"french_bitoduc"
,"nepali"
,"nepali_1k"
,"nepali_romanized"
,"sanskrit"
,"sanskrit_roman"
,"santali"
,"azerbaijani"
,"azerbaijani_1k"
,"arabic"
,"arabic_10k"
,"arabic_egypt"
,"arabic_egypt_1k"
,"malagasy"
,"malagasy_1k"
,"malay"
,"malay_1k"
,"mongolian"
,"mongolian_10k"
,"kannada"
,"korean"
,"korean_1k"
,"korean_5k"
,"khmer"
,"chinese_simplified"
,"chinese_simplified_1k"
,"chinese_simplified_5k"
,"chinese_simplified_10k"
,"chinese_simplified_50k"
,"chinese_traditional"
,"russian"
,"russian_1k"
,"russian_5k"
,"russian_10k"
,"russian_25k"
,"russian_50k"
,"russian_375k"
,"russian_contractions"
,"russian_contractions_1k"
,"russian_abbreviations"
,"ukrainian"
,"ukrainian_1k"
,"ukrainian_10k"
,"ukrainian_50k"
,"ukrainian_endings"
,"ukrainian_latynka"
,"ukrainian_latynka_1k"
,"ukrainian_latynka_10k"
,"ukrainian_latynka_50k"
,"ukrainian_latynka_endings"
,"portuguese"
,"portuguese_acentos_e_cedilha"
,"portuguese_1k"
,"portuguese_3k"
,"portuguese_5k"
,"portuguese_320k"
,"portuguese_550k"
,"indonesian"
,"indonesian_1k"
,"indonesian_10k"
,"kurdish_central"
,"kurdish_central_2k"
,"kurdish_central_4k"
,"german"
,"german_1k"
,"german_10k"
,"german_250k"
,"swiss_german"
,"swiss_german_1k"
,"swiss_german_2k"
,"afrikaans"
,"afrikaans_1k"
,"afrikaans_10k"
,"georgian"
,"tamil"
,"tamil_1k"
,"tanglish"
,"tamil_old"
,"telugu"
,"telugu_1k"
,"greek"
,"greek_1k"
,"greek_5k"
,"greek_10k"
,"greek_25k"
,"greeklish"
,"greeklish_1k"
,"greeklish_5k"
,"greeklish_10k"
,"greeklish_25k"
,"turkish"
,"turkish_1k"
,"turkish_5k"
,"irish"
,"italian"
,"italian_1k"
,"italian_7k"
,"italian_60k"
,"italian_280k"
,"friulian"
,"latin"
,"galician"
,"thai"
,"thai_20k"
,"polish"
,"polish_2k"
,"polish_5k"
,"polish_10k"
,"polish_20k"
,"polish_40k"
,"polish_200k"
,"czech"
,"czech_1k"
,"czech_10k"
,"slovak"
,"slovak_1k"
,"slovak_10k"
,"slovenian"
,"slovenian_1k"
,"slovenian_5k"
,"croatian"
,"croatian_1k"
,"dutch"
,"dutch_1k"
,"dutch_10k"
,"filipino"
,"filipino_1k"
,"danish"
,"danish_1k"
,"danish_10k"
,"hungarian"
,"hungarian_2k"
,"norwegian_bokmal"
,"norwegian_bokmal_1k"
,"norwegian_bokmal_5k"
,"norwegian_bokmal_10k"
,"norwegian_bokmal_150k"
,"norwegian_bokmal_600k"
,"norwegian_nynorsk"
,"norwegian_nynorsk_1k"
,"norwegian_nynorsk_5k"
,"norwegian_nynorsk_10k"
,"norwegian_nynorsk_100k"
,"norwegian_nynorsk_400k"
,"hebrew"
,"hebrew_1k"
,"hebrew_5k"
,"hebrew_10k"
,"icelandic_1k"
,"romanian"
,"romanian_1k"
,"romanian_5k"
,"romanian_10k"
,"romanian_25k"
,"romanian_50k"
,"romanian_100k"
,"romanian_200k"
,"lorem_ipsum"
,"finnish"
,"finnish_1k"
,"finnish_10k"
,"estonian"
,"estonian_1k"
,"estonian_5k"
,"estonian_10k"
,"udmurt"
,"welsh"
,"welsh_1k"
,"persian"
,"persian_1k"
,"persian_5k"
,"persian_20k"
,"persian_romanized"
,"marathi"
,"kazakh"
,"kazakh_1k"
,"vietnamese"
,"vietnamese_1k"
,"vietnamese_5k"
,"jyutping"
,"pinyin"
,"pinyin_1k"
,"pinyin_10k"
,"hausa"
,"hausa_1k"
,"swedish"
,"swedish_1k"
,"swedish_diacritics"
,"serbian_latin"
,"serbian_latin_10k"
,"serbian"
,"serbian_10k"
,"yoruba_1k"
,"swahili_1k"
,"maori_1k"
,"catalan"
,"catalan_1k"
,"lojban_gismu"
,"lojban_cmavo"
,"lithuanian"
,"lithuanian_1k"
,"lithuanian_3k"
,"bulgarian"
,"bulgarian_latin"
,"bangla"
,"bangla_letters"
,"bangla_10k"
,"bosnian"
,"bosnian_4k"
,"toki_pona"
,"toki_pona_ku_suli"
,"toki_pona_ku_lili"
,"esperanto"
,"esperanto_1k"
,"esperanto_10k"
,"esperanto_25k"
,"esperanto_36k"
,"esperanto_x_sistemo"
,"esperanto_x_sistemo_1k"
,"esperanto_x_sistemo_10k"
,"esperanto_x_sistemo_25k"
,"esperanto_x_sistemo_36k"
,"esperanto_h_sistemo"
,"esperanto_h_sistemo_1k"
,"esperanto_h_sistemo_10k"
,"esperanto_h_sistemo_25k"
,"esperanto_h_sistemo_36k"
,"kyrgyz"
,"kyrgyz_1k"
,"urdu"
,"urdu_1k"
,"urdu_5k"
,"urdish"
,"albanian"
,"albanian_1k"
,"shona"
,"shona_1k"
,"armenian"
,"armenian_1k"
,"armenian_western"
,"armenian_western_1k"
,"myanmar_burmese"
,"japanese_hiragana"
,"japanese_katakana"
,"japanese_romaji"
,"japanese_romaji_1k"
,"sinhala"
,"latvian"
,"latvian_1k"
,"maltese"
,"maltese_1k"
,"twitch_emotes"
,"git"
,"pig_latin"
,"hindi"
,"hindi_1k"
,"hinglish"
,"gujarati"
,"gujarati_1k"
,"macedonian"
,"macedonian_1k"
,"macedonian_10k"
,"macedonian_75k"
,"belarusian"
,"belarusian_1k"
,"belarusian_5k"
,"belarusian_10k"
,"belarusian_25k"
,"belarusian_50k"
,"belarusian_100k"
,"belarusian_lacinka"
,"belarusian_lacinka_1k"
,"tatar"
,"tatar_1k"
,"tatar_5k"
,"tatar_9k"
,"tatar_crimean"
,"tatar_crimean_1k"
,"tatar_crimean_5k"
,"tatar_crimean_10k"
,"tatar_crimean_15k"
,"tatar_crimean_cyrillic"
,"tatar_crimean_cyrillic_1k"
,"tatar_crimean_cyrillic_5k"
,"tatar_crimean_cyrillic_10k"
,"tatar_crimean_cyrillic_15k"
,"uzbek"
,"uzbek_1k"
,"uzbek_70k"
,"malayalam"
,"amharic"
,"amharic_1k"
,"amharic_5k"
,"oromo"
,"oromo_1k"
,"oromo_5k"
,"wordle"
,"league_of_legends"
,"wordle_1k"
,"typing_of_the_dead"
,"yiddish"
,"frisian"
,"frisian_1k"
,"pashto"
,"euskera"
,"klingon"
,"klingon_1k"
,"quenya"
,"occitan"
,"occitan_1k"
,"occitan_2k"
,"occitan_5k"
,"occitan_10k"
,"bashkir"
,"zulu"
,"code_python"
,"code_python_1k"
,"code_python_2k"
,"code_python_5k"
,"code_fsharp"
,"code_c"
,"code_csharp"
,"code_css"
,"code_c++"
,"code_dart"
,"code_brainfck"
,"code_javascript"
,"code_javascript_1k"
,"code_javascript_react"
,"code_jule"
,"code_julia"
,"code_haskell"
,"code_html"
,"code_nim"
,"code_nix"
,"code_pascal"
,"code_java"
,"code_kotlin"
,"code_go"
,"code_rockstar"
,"code_rust"
,"code_ruby"
,"code_r"
,"code_r_2k"
,"code_swift"
,"code_scala"
,"code_bash"
,"code_powershell"
,"code_lua"
,"code_luau"
,"code_latex"
,"code_typst"
,"code_matlab"
,"code_sql"
,"code_perl"
,"code_php"
,"code_vim"
,"code_vimscript"
,"code_opencl"
,"code_visual_basic"
,"code_arduino"
,"code_systemverilog"
,"code_elixir"
,"code_zig"
,"code_gdscript"
,"code_gdscript_2"
,"code_assembly"
,"code_v"
,"code_ook"
,"code_typescript"
,"code_odin"
,"xhosa"
,"xhosa_3k"
,"tibetan"
,"tibetan_1k"
,"code_cobol"
,"code_common_lisp"
,"docker_file"
,"code_fortran"
]

View file

@ -1,3 +0,0 @@
// chinese: {leftToRight: true, words: ["一","人","里","会","没","她","吗","去","也","有","这","那","不","什","个","来","要","就","我","你","的","是","了","他","么","们","在","说","为","好","吧","知道","我的","和","你的","想","只","很","都","对","把","啊","怎","得","还","过","不是","到","样","飞","远","身","任何","生活","够","号","兰","瑞","达","或","愿","蒂","別","军","正","是不是","证","不用","三","乐","吉","男人","告訴","路","搞","可是","与","次","狗","决","金","史","姆","部","正在","活","刚","回家","贝","如何","须","战","不會","夫","喂","父","亚","肯定","女孩","世界","不要","些","不知道","不能","因","觉","发","像","太","但是","多","打","机","來","好了","用","他的","诉","德","叫","什麼","真","干","心","走","比","死","嘿","出","车","一下","中","好吧","需要","经","妈","候","长","而","错","好的","间","又","国","起","动","杀","于","种","去了","担","名","混蛋","礼","幹","不了","有些","過","後","击","漂亮","神","多少","海","每","哥","教","走吧","好像","单","公","林","女","忙","火","钟","家伙","科","回去","最后","水","不管","麦","泻","鬼","還","船","永","安全","那個","爾","這麼","满","风","皮","威","据","鲁","转","相"]},
// chinese_simplified: {leftToRight: true, words: ["我","的","你","是","了","不","们","这","一","他","么","在","有","个","好","来","人","那","要","会","就","什","没","到","说","吗","为","想","能","上","去","道","她","很","看","可","知","得","过","吧","还","对","里","以","都","事","子","生","时","样","也","和","下","真","现","做","大","啊","怎","出","点","起","天","把","开","让","给","但","谢","着","只","些","如","家","后","儿","多","意","别","所","话","小","自","回","然","果","发","见","心","走","定","听","觉","太","该","当","经","妈","用","打","地","再","因","呢","女","告","最","手","前","找","行","快","而","死","先","像","等","被","从","明","中","哦","情","作","跟","面","诉","爱","已","之","问","错","孩","斯","成","它","感","干","法","电","间","哪","西","己","候","次","信","欢","正","实","关","进","车","年","喜","认","克","爸","谁","方","老","应","比","帮","无","晚","动","头","机","分","特","相","全","杀","需","放","常","直","才","美","于","带","今","力","工","许","东","名","同","长","亲","种","者","嘿","白","学","安","尔","叫","理"]},
// korean: {leftToRight: true, words: ["로","나는","그의","그","그","했다","에 대한","에","아르","와","그들","있다","에","일","이","이","부터","에 의해","뜨거운","단어","하지만","무엇","다소","이다","그","당신","또는","했다","에","의","에","과","이","에","우리","수","아웃","다른","했다","하는","할","자신의","시간","면","것","방법","말했다","이","각","이야기","하지","세트","세","필요","공기","잘","또한","재생","작은","끝","넣어","홈","읽기","손","포트","큰","철자","추가","도","땅","여기","해야","큰","높은","이러한","따라","행위","이유","문의","남자","변경","갔다","빛","종류","오프","필요가있다","집","사진","시험","우리","다시","동물","포인트","어머니","세계","가까운","구축","자기","지구","아버지","모든","새로운","일","일부","소요","도착","장소","만든","살고있다","어디에","후","다시","작은","만","둥근","사람","년","온","쇼","모든","좋은","나를","제공","우리의","아래의","이름","대단히","를 통해","단지","양식","문장","큰","생각","말","도움","낮은","온라인","차이","회전","원인","많은","의미","이전","움직임","바로","소년","늙은","너무","동일","그녀","모든","그곳에","때","올라","사용","당신의","방법","에 대한","많은","다음","그","쓰기","것","같은","그래서","이들","그녀의","긴","확인","일","참조","그","두","이","봐","더","일","수","이동","올","한","수","소리","없음","가장","사람들","내","이상","알고","물","보다","통화","첫째","사람","수도","아래로","측면","하고","지금","발견"]},

View file

@ -55,7 +55,7 @@
"pretty-fix-pkg": "prettier --write ./packages",
"pr-check-lint-json": "cd frontend && eslint './static/**/*.json'",
"pr-check-quote-json": "cd frontend && npx gulp pr-check-quote-json",
"pr-check-language-json": "cd frontend && npx gulp pr-check-language-json",
"pr-check-language-json": "cd frontend && npx gulp pr-check-language-json && turbo test -- constants/languages",
"pr-check-other-json": "cd frontend && npx gulp pr-check-other-json && turbo test -- constants/layouts constants/themes"
},
"engines": {

View file

@ -9,9 +9,9 @@ import {
LeaderboardEntrySchema,
XpLeaderboardEntrySchema,
} from "./schemas/leaderboards";
import { LanguageSchema } from "./schemas/util";
import { Mode2Schema, ModeSchema } from "./schemas/shared";
import { initContract } from "@ts-rest/core";
import { LanguageSchema } from "./schemas/languages";
const LanguageAndModeQuerySchema = z.object({
language: LanguageSchema,

View file

@ -3,7 +3,7 @@ import { z } from "zod";
import { CommonResponses, meta, responseWithData } from "./schemas/api";
import { SpeedHistogramSchema, TypingStatsSchema } from "./schemas/public";
import { Mode2Schema, ModeSchema } from "./schemas/shared";
import { LanguageSchema } from "./schemas/util";
import { LanguageSchema } from "./schemas/languages";
export const GetSpeedHistogramQuerySchema = z
.object({

View file

@ -15,7 +15,8 @@ import {
QuoteReportReasonSchema,
QuoteSchema,
} from "./schemas/quotes";
import { IdSchema, LanguageSchema, NullableStringSchema } from "./schemas/util";
import { IdSchema, NullableStringSchema } from "./schemas/util";
import { LanguageSchema } from "./schemas/languages";
export const GetQuotesResponseSchema = responseWithData(z.array(QuoteSchema));
export type GetQuotesResponse = z.infer<typeof GetQuotesResponseSchema>;

View file

@ -1,8 +1,8 @@
import { z, ZodSchema } from "zod";
import { LanguageSchema } from "./util";
import * as Shared from "./shared";
import * as Themes from "./themes";
import * as Layouts from "./layouts";
import { LanguageSchema } from "./languages";
export const SmoothCaretSchema = z.enum(["off", "slow", "medium", "fast"]);
export type SmoothCaret = z.infer<typeof SmoothCaretSchema>;

View file

@ -0,0 +1,417 @@
import { z } from "zod";
import { customEnumErrorHandler } from "./util";
export const LanguageSchema = z.enum(
[
"english",
"english_1k",
"english_5k",
"english_10k",
"english_25k",
"english_450k",
"english_commonly_misspelled",
"english_contractions",
"english_doubleletter",
"english_shakespearean",
"english_old",
"english_medical",
"spanish",
"spanish_1k",
"spanish_10k",
"spanish_650k",
"french",
"french_1k",
"french_2k",
"french_10k",
"french_600k",
"french_bitoduc",
"nepali",
"nepali_1k",
"nepali_romanized",
"sanskrit",
"sanskrit_roman",
"santali",
"azerbaijani",
"azerbaijani_1k",
"arabic",
"arabic_10k",
"arabic_egypt",
"arabic_egypt_1k",
"malagasy",
"malagasy_1k",
"malay",
"malay_1k",
"mongolian",
"mongolian_10k",
"kannada",
"korean",
"korean_1k",
"korean_5k",
"khmer",
"chinese_simplified",
"chinese_simplified_1k",
"chinese_simplified_5k",
"chinese_simplified_10k",
"chinese_simplified_50k",
"chinese_traditional",
"russian",
"russian_1k",
"russian_5k",
"russian_10k",
"russian_25k",
"russian_50k",
"russian_375k",
"russian_contractions",
"russian_contractions_1k",
"russian_abbreviations",
"ukrainian",
"ukrainian_1k",
"ukrainian_10k",
"ukrainian_50k",
"ukrainian_endings",
"ukrainian_latynka",
"ukrainian_latynka_1k",
"ukrainian_latynka_10k",
"ukrainian_latynka_50k",
"ukrainian_latynka_endings",
"portuguese",
"portuguese_acentos_e_cedilha",
"portuguese_1k",
"portuguese_3k",
"portuguese_5k",
"portuguese_320k",
"portuguese_550k",
"indonesian",
"indonesian_1k",
"indonesian_10k",
"kurdish_central",
"kurdish_central_2k",
"kurdish_central_4k",
"german",
"german_1k",
"german_10k",
"german_250k",
"swiss_german",
"swiss_german_1k",
"swiss_german_2k",
"afrikaans",
"afrikaans_1k",
"afrikaans_10k",
"georgian",
"tamil",
"tamil_1k",
"tanglish",
"tamil_old",
"telugu",
"telugu_1k",
"greek",
"greek_1k",
"greek_5k",
"greek_10k",
"greek_25k",
"greeklish",
"greeklish_1k",
"greeklish_5k",
"greeklish_10k",
"greeklish_25k",
"turkish",
"turkish_1k",
"turkish_5k",
"irish",
"italian",
"italian_1k",
"italian_7k",
"italian_60k",
"italian_280k",
"friulian",
"latin",
"galician",
"thai",
"thai_20k",
"polish",
"polish_2k",
"polish_5k",
"polish_10k",
"polish_20k",
"polish_40k",
"polish_200k",
"czech",
"czech_1k",
"czech_10k",
"slovak",
"slovak_1k",
"slovak_10k",
"slovenian",
"slovenian_1k",
"slovenian_5k",
"croatian",
"croatian_1k",
"dutch",
"dutch_1k",
"dutch_10k",
"filipino",
"filipino_1k",
"danish",
"danish_1k",
"danish_10k",
"hungarian",
"hungarian_2k",
"norwegian_bokmal",
"norwegian_bokmal_1k",
"norwegian_bokmal_5k",
"norwegian_bokmal_10k",
"norwegian_bokmal_150k",
"norwegian_bokmal_600k",
"norwegian_nynorsk",
"norwegian_nynorsk_1k",
"norwegian_nynorsk_5k",
"norwegian_nynorsk_10k",
"norwegian_nynorsk_100k",
"norwegian_nynorsk_400k",
"hebrew",
"hebrew_1k",
"hebrew_5k",
"hebrew_10k",
"icelandic_1k",
"romanian",
"romanian_1k",
"romanian_5k",
"romanian_10k",
"romanian_25k",
"romanian_50k",
"romanian_100k",
"romanian_200k",
"lorem_ipsum",
"finnish",
"finnish_1k",
"finnish_10k",
"estonian",
"estonian_1k",
"estonian_5k",
"estonian_10k",
"udmurt",
"welsh",
"welsh_1k",
"persian",
"persian_1k",
"persian_5k",
"persian_20k",
"persian_romanized",
"marathi",
"kazakh",
"kazakh_1k",
"vietnamese",
"vietnamese_1k",
"vietnamese_5k",
"jyutping",
"pinyin",
"pinyin_1k",
"pinyin_10k",
"hausa",
"hausa_1k",
"swedish",
"swedish_1k",
"swedish_diacritics",
"serbian_latin",
"serbian_latin_10k",
"serbian",
"serbian_10k",
"yoruba_1k",
"swahili_1k",
"maori_1k",
"catalan",
"catalan_1k",
"lojban_gismu",
"lojban_cmavo",
"lithuanian",
"lithuanian_1k",
"lithuanian_3k",
"bulgarian",
"bulgarian_latin",
"bangla",
"bangla_letters",
"bangla_10k",
"bosnian",
"bosnian_4k",
"toki_pona",
"toki_pona_ku_suli",
"toki_pona_ku_lili",
"esperanto",
"esperanto_1k",
"esperanto_10k",
"esperanto_25k",
"esperanto_36k",
"esperanto_x_sistemo",
"esperanto_x_sistemo_1k",
"esperanto_x_sistemo_10k",
"esperanto_x_sistemo_25k",
"esperanto_x_sistemo_36k",
"esperanto_h_sistemo",
"esperanto_h_sistemo_1k",
"esperanto_h_sistemo_10k",
"esperanto_h_sistemo_25k",
"esperanto_h_sistemo_36k",
"kyrgyz",
"kyrgyz_1k",
"urdu",
"urdu_1k",
"urdu_5k",
"urdish",
"albanian",
"albanian_1k",
"shona",
"shona_1k",
"armenian",
"armenian_1k",
"armenian_western",
"armenian_western_1k",
"myanmar_burmese",
"japanese_hiragana",
"japanese_katakana",
"japanese_romaji",
"japanese_romaji_1k",
"sinhala",
"latvian",
"latvian_1k",
"maltese",
"maltese_1k",
"twitch_emotes",
"git",
"pig_latin",
"hindi",
"hindi_1k",
"hinglish",
"gujarati",
"gujarati_1k",
"macedonian",
"macedonian_1k",
"macedonian_10k",
"macedonian_75k",
"belarusian",
"belarusian_1k",
"belarusian_5k",
"belarusian_10k",
"belarusian_25k",
"belarusian_50k",
"belarusian_100k",
"belarusian_lacinka",
"belarusian_lacinka_1k",
"tatar",
"tatar_1k",
"tatar_5k",
"tatar_9k",
"tatar_crimean",
"tatar_crimean_1k",
"tatar_crimean_5k",
"tatar_crimean_10k",
"tatar_crimean_15k",
"tatar_crimean_cyrillic",
"tatar_crimean_cyrillic_1k",
"tatar_crimean_cyrillic_5k",
"tatar_crimean_cyrillic_10k",
"tatar_crimean_cyrillic_15k",
"uzbek",
"uzbek_1k",
"uzbek_70k",
"malayalam",
"amharic",
"amharic_1k",
"amharic_5k",
"oromo",
"oromo_1k",
"oromo_5k",
"wordle",
"league_of_legends",
"wordle_1k",
"typing_of_the_dead",
"yiddish",
"frisian",
"frisian_1k",
"pashto",
"euskera",
"klingon",
"klingon_1k",
"quenya",
"occitan",
"occitan_1k",
"occitan_2k",
"occitan_5k",
"occitan_10k",
"bashkir",
"zulu",
"kabyle",
"kabyle_1k",
"kabyle_2k",
"kabyle_5k",
"kabyle_10k",
"code_python",
"code_python_1k",
"code_python_2k",
"code_python_5k",
"code_fsharp",
"code_c",
"code_csharp",
"code_css",
"code_c++",
"code_dart",
"code_brainfck",
"code_javascript",
"code_javascript_1k",
"code_javascript_react",
"code_jule",
"code_julia",
"code_haskell",
"code_html",
"code_nim",
"code_nix",
"code_pascal",
"code_java",
"code_kotlin",
"code_go",
"code_rockstar",
"code_rust",
"code_ruby",
"code_r",
"code_r_2k",
"code_swift",
"code_scala",
"code_bash",
"code_powershell",
"code_lua",
"code_luau",
"code_latex",
"code_typst",
"code_matlab",
"code_sql",
"code_perl",
"code_php",
"code_vim",
"code_vimscript",
"code_opencl",
"code_visual_basic",
"code_arduino",
"code_systemverilog",
"code_elixir",
"code_zig",
"code_gdscript",
"code_gdscript_2",
"code_assembly",
"code_v",
"code_ook",
"code_typescript",
"code_odin",
"xhosa",
"xhosa_3k",
"tibetan",
"tibetan_1k",
"code_cobol",
"code_common_lisp",
"docker_file",
"code_fortran",
],
{
errorMap: customEnumErrorHandler("Must be a supported language"),
}
);
export type Language = z.infer<typeof LanguageSchema>;

View file

@ -1,5 +1,6 @@
import { z } from "zod";
import { IdSchema, LanguageSchema } from "./util";
import { IdSchema } from "./util";
import { LanguageSchema } from "./languages";
export const QuoteIdSchema = z
.number()

View file

@ -6,8 +6,8 @@ import {
PercentageSchema,
token,
WpmSchema,
LanguageSchema,
} from "./util";
import { LanguageSchema } from "./languages";
import { Mode, Mode2, Mode2Schema, ModeSchema } from "./shared";
import { DifficultySchema, FunboxSchema } from "./configs";

View file

@ -1,5 +1,6 @@
import { literal, z } from "zod";
import { StringNumberSchema } from "./util";
import { LanguageSchema } from "./languages";
//used by config and shared
export const DifficultySchema = z.enum(["normal", "expert", "master"]);
@ -11,10 +12,7 @@ export const PersonalBestSchema = z.object({
consistency: z.number().nonnegative().max(100),
difficulty: DifficultySchema,
lazyMode: z.boolean().optional(),
language: z
.string()
.max(100)
.regex(/[\w+]+/),
language: LanguageSchema,
punctuation: z.boolean().optional(),
numbers: z.boolean().optional(),
raw: z.number().nonnegative(),

View file

@ -1,188 +1,194 @@
import { z } from "zod";
import { customEnumErrorHandler } from "./util";
export const ThemeNameSchema = z.enum([
"8008",
"80s_after_dark",
"9009",
"aether",
"alduin",
"alpine",
"anti_hero",
"arch",
"aurora",
"beach",
"bento",
"bingsu",
"bliss",
"blue_dolphin",
"blueberry_dark",
"blueberry_light",
"botanical",
"bouquet",
"breeze",
"bushido",
"cafe",
"camping",
"carbon",
"catppuccin",
"chaos_theory",
"cheesecake",
"cherry_blossom",
"comfy",
"copper",
"creamsicle",
"cy_red",
"cyberspace",
"dark",
"dark_magic_girl",
"dark_note",
"darling",
"deku",
"desert_oasis",
"dev",
"diner",
"dino",
"discord",
"dmg",
"dollar",
"dots",
"dracula",
"drowning",
"dualshot",
"earthsong",
"everblush",
"evil_eye",
"ez_mode",
"fire",
"fledgling",
"fleuriste",
"floret",
"froyo",
"frozen_llama",
"fruit_chew",
"fundamentals",
"future_funk",
"github",
"godspeed",
"graen",
"grand_prix",
"grape",
"gruvbox_dark",
"gruvbox_light",
"hammerhead",
"hanok",
"hedge",
"honey",
"horizon",
"husqy",
"iceberg_dark",
"iceberg_light",
"incognito",
"ishtar",
"iv_clover",
"iv_spade",
"joker",
"laser",
"lavender",
"leather",
"lil_dragon",
"lilac_mist",
"lime",
"luna",
"macroblank",
"magic_girl",
"mashu",
"matcha_moccha",
"material",
"matrix",
"menthol",
"metaverse",
"metropolis",
"mexican",
"miami",
"miami_nights",
"midnight",
"milkshake",
"mint",
"mizu",
"modern_dolch",
"modern_dolch_light",
"modern_ink",
"monokai",
"moonlight",
"mountain",
"mr_sleeves",
"ms_cupcakes",
"muted",
"nautilus",
"nebula",
"night_runner",
"nord",
"nord_light",
"norse",
"oblivion",
"olive",
"olivia",
"onedark",
"our_theme",
"paper",
"passion_fruit",
"pastel",
"peach_blossom",
"peaches",
"phantom",
"pink_lemonade",
"pulse",
"purpleish",
"rainbow_trail",
"red_dragon",
"red_samurai",
"repose_dark",
"repose_light",
"retro",
"retrocast",
"rgb",
"rose_pine",
"rose_pine_dawn",
"rose_pine_moon",
"rudy",
"ryujinscales",
"serika",
"serika_dark",
"sewing_tin",
"sewing_tin_light",
"shadow",
"shoko",
"slambook",
"snes",
"soaring_skies",
"solarized_dark",
"solarized_light",
"solarized_osaka",
"sonokai",
"stealth",
"strawberry",
"striker",
"suisei",
"sunset",
"superuser",
"sweden",
"tangerine",
"taro",
"terminal",
"terra",
"terrazzo",
"terror_below",
"tiramisu",
"trackday",
"trance",
"tron_orange",
"vaporwave",
"vesper",
"viridescent",
"voc",
"vscode",
"watermelon",
"wavez",
"witch_girl",
]);
export const ThemeNameSchema = z.enum(
[
"8008",
"80s_after_dark",
"9009",
"aether",
"alduin",
"alpine",
"anti_hero",
"arch",
"aurora",
"beach",
"bento",
"bingsu",
"bliss",
"blue_dolphin",
"blueberry_dark",
"blueberry_light",
"botanical",
"bouquet",
"breeze",
"bushido",
"cafe",
"camping",
"carbon",
"catppuccin",
"chaos_theory",
"cheesecake",
"cherry_blossom",
"comfy",
"copper",
"creamsicle",
"cy_red",
"cyberspace",
"dark",
"dark_magic_girl",
"dark_note",
"darling",
"deku",
"desert_oasis",
"dev",
"diner",
"dino",
"discord",
"dmg",
"dollar",
"dots",
"dracula",
"drowning",
"dualshot",
"earthsong",
"everblush",
"evil_eye",
"ez_mode",
"fire",
"fledgling",
"fleuriste",
"floret",
"froyo",
"frozen_llama",
"fruit_chew",
"fundamentals",
"future_funk",
"github",
"godspeed",
"graen",
"grand_prix",
"grape",
"gruvbox_dark",
"gruvbox_light",
"hammerhead",
"hanok",
"hedge",
"honey",
"horizon",
"husqy",
"iceberg_dark",
"iceberg_light",
"incognito",
"ishtar",
"iv_clover",
"iv_spade",
"joker",
"laser",
"lavender",
"leather",
"lil_dragon",
"lilac_mist",
"lime",
"luna",
"macroblank",
"magic_girl",
"mashu",
"matcha_moccha",
"material",
"matrix",
"menthol",
"metaverse",
"metropolis",
"mexican",
"miami",
"miami_nights",
"midnight",
"milkshake",
"mint",
"mizu",
"modern_dolch",
"modern_dolch_light",
"modern_ink",
"monokai",
"moonlight",
"mountain",
"mr_sleeves",
"ms_cupcakes",
"muted",
"nautilus",
"nebula",
"night_runner",
"nord",
"nord_light",
"norse",
"oblivion",
"olive",
"olivia",
"onedark",
"our_theme",
"paper",
"passion_fruit",
"pastel",
"peach_blossom",
"peaches",
"phantom",
"pink_lemonade",
"pulse",
"purpleish",
"rainbow_trail",
"red_dragon",
"red_samurai",
"repose_dark",
"repose_light",
"retro",
"retrocast",
"rgb",
"rose_pine",
"rose_pine_dawn",
"rose_pine_moon",
"rudy",
"ryujinscales",
"serika",
"serika_dark",
"sewing_tin",
"sewing_tin_light",
"shadow",
"shoko",
"slambook",
"snes",
"soaring_skies",
"solarized_dark",
"solarized_light",
"solarized_osaka",
"sonokai",
"stealth",
"strawberry",
"striker",
"suisei",
"sunset",
"superuser",
"sweden",
"tangerine",
"taro",
"terminal",
"terra",
"terrazzo",
"terror_below",
"tiramisu",
"trackday",
"trance",
"tron_orange",
"vaporwave",
"vesper",
"viridescent",
"voc",
"vscode",
"watermelon",
"wavez",
"witch_girl",
],
{
errorMap: customEnumErrorHandler("Must be a known theme"),
}
);

View file

@ -1,5 +1,6 @@
import { z, ZodEffects, ZodOptional, ZodString } from "zod";
import { IdSchema, LanguageSchema, StringNumberSchema } from "./util";
import { IdSchema, StringNumberSchema } from "./util";
import { LanguageSchema } from "./languages";
import {
ModeSchema,
Mode2Schema,

View file

@ -1,4 +1,4 @@
import { z, ZodString } from "zod";
import { z, ZodErrorMap, ZodString } from "zod";
export const StringNumberSchema = z
.string()
@ -17,12 +17,6 @@ export type Id = z.infer<typeof IdSchema>;
export const TagSchema = token().max(50);
export type Tag = z.infer<typeof TagSchema>;
export const LanguageSchema = z
.string()
.max(50)
.regex(/^[a-zA-Z0-9_+]+$/, "Can only contain letters [a-zA-Z0-9_+]");
export type Language = z.infer<typeof LanguageSchema>;
export const NullableStringSchema = z
.string()
.nullable()
@ -41,3 +35,12 @@ export type CustomTextMode = z.infer<typeof CustomTextModeSchema>;
export const CustomTextLimitModeSchema = z.enum(["word", "time", "section"]);
export type CustomTextLimitMode = z.infer<typeof CustomTextLimitModeSchema>;
export function customEnumErrorHandler(message: string): ZodErrorMap {
return (issue, _ctx) => ({
message:
issue.code === "invalid_enum_value"
? `Invalid enum value. ${message}`
: issue.message ?? "Required",
});
}

View file

@ -26,7 +26,8 @@ import {
UserTagSchema,
} from "./schemas/users";
import { Mode2Schema, ModeSchema, PersonalBestSchema } from "./schemas/shared";
import { IdSchema, LanguageSchema, StringNumberSchema } from "./schemas/util";
import { IdSchema, StringNumberSchema } from "./schemas/util";
import { LanguageSchema } from "./schemas/languages";
import { CustomThemeColorsSchema } from "./schemas/configs";
import { doesNotContainProfanity } from "./validation/validation";