R1: schema & naming reset — squash migrations, rename variants
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 37s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
Squash the 12 goose migrations into one 00001_baseline.sql (there is no prod data; verified schema-identical to the chain via a pg_dump diff + the green integration suite) and rename the game-variant labels english/russian_scrabble/erudit -> scrabble_en/scrabble_ru/erudit_ru across the backend, the FlatBuffers wire values and the UI. dawg filenames and the Go enum identifiers are unchanged; the i18n display keys are kept. Adds PRERELEASE.md (the R1-R7 pre-release tracker), linked from CLAUDE.md. Contour DB wipe and the scrabble-dictionary tidy are follow-ups.
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
// complaint, off the board so the soft keyboard never relayouts the play area.
|
||||
let { id }: { id: string } = $props();
|
||||
|
||||
let variant = $state<Variant>('english');
|
||||
let variant = $state<Variant>('scrabble_en');
|
||||
let word = $state('');
|
||||
let result = $state<{ word: string; legal: boolean } | null>(null);
|
||||
let cooling = $state(false);
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
let resignOpen = $state(false);
|
||||
let drag = $state<{ letter: string; blank: boolean; x: number; y: number; touch: boolean } | null>(null);
|
||||
|
||||
const variant = $derived(view?.game.variant ?? 'english');
|
||||
const variant = $derived(view?.game.variant ?? 'scrabble_en');
|
||||
const board = $derived(replay(moves));
|
||||
const premium = $derived(premiumGrid(variant));
|
||||
const ctr = $derived(centre(variant));
|
||||
|
||||
+20
-20
@@ -12,37 +12,37 @@ import {
|
||||
// The cache module is per-file-isolated by vitest, so only what these tests seed exists.
|
||||
describe('alphabet cache (Stage 13)', () => {
|
||||
it('upper-cases letters for display and maps indices and values case-insensitively', () => {
|
||||
setAlphabet('english', [
|
||||
setAlphabet('scrabble_en', [
|
||||
{ index: 0, letter: 'a', value: 1 },
|
||||
{ index: 16, letter: 'q', value: 10 },
|
||||
]);
|
||||
expect(hasAlphabet('english')).toBe(true);
|
||||
expect(letterForIndex('english', 0)).toBe('A');
|
||||
expect(letterForIndex('english', 16)).toBe('Q');
|
||||
expect(indexForLetter('english', 'a')).toBe(0);
|
||||
expect(indexForLetter('english', 'Q')).toBe(16);
|
||||
expect(valueForLetter('english', 'a')).toBe(1);
|
||||
expect(valueForLetter('english', 'Q')).toBe(10);
|
||||
expect(hasAlphabet('scrabble_en')).toBe(true);
|
||||
expect(letterForIndex('scrabble_en', 0)).toBe('A');
|
||||
expect(letterForIndex('scrabble_en', 16)).toBe('Q');
|
||||
expect(indexForLetter('scrabble_en', 'a')).toBe(0);
|
||||
expect(indexForLetter('scrabble_en', 'Q')).toBe(16);
|
||||
expect(valueForLetter('scrabble_en', 'a')).toBe(1);
|
||||
expect(valueForLetter('scrabble_en', 'Q')).toBe(10);
|
||||
});
|
||||
|
||||
it('handles the blank sentinel and unknown letters/indices', () => {
|
||||
setAlphabet('english', [{ index: 0, letter: 'a', value: 1 }]);
|
||||
expect(letterForIndex('english', BLANK_INDEX)).toBe('?');
|
||||
expect(indexForLetter('english', '?')).toBe(BLANK_INDEX);
|
||||
expect(valueForLetter('english', '?')).toBe(0);
|
||||
expect(letterForIndex('english', 99)).toBe(''); // out of range
|
||||
expect(valueForLetter('english', 'Z')).toBe(0); // not in this alphabet
|
||||
expect(() => indexForLetter('english', 'Z')).toThrow();
|
||||
setAlphabet('scrabble_en', [{ index: 0, letter: 'a', value: 1 }]);
|
||||
expect(letterForIndex('scrabble_en', BLANK_INDEX)).toBe('?');
|
||||
expect(indexForLetter('scrabble_en', '?')).toBe(BLANK_INDEX);
|
||||
expect(valueForLetter('scrabble_en', '?')).toBe(0);
|
||||
expect(letterForIndex('scrabble_en', 99)).toBe(''); // out of range
|
||||
expect(valueForLetter('scrabble_en', 'Z')).toBe(0); // not in this alphabet
|
||||
expect(() => indexForLetter('scrabble_en', 'Z')).toThrow();
|
||||
});
|
||||
|
||||
it('lists the alphabet for the blank chooser and is empty for an uncached variant', () => {
|
||||
setAlphabet('english', [
|
||||
setAlphabet('scrabble_en', [
|
||||
{ index: 0, letter: 'a', value: 1 },
|
||||
{ index: 1, letter: 'b', value: 3 },
|
||||
]);
|
||||
expect(alphabetLetters('english')).toEqual(['A', 'B']);
|
||||
expect(hasAlphabet('erudit')).toBe(false);
|
||||
expect(alphabetLetters('erudit')).toEqual([]);
|
||||
expect(valueForLetter('erudit', 'A')).toBe(0);
|
||||
expect(alphabetLetters('scrabble_en')).toEqual(['A', 'B']);
|
||||
expect(hasAlphabet('erudit_ru')).toBe(false);
|
||||
expect(alphabetLetters('erudit_ru')).toEqual([]);
|
||||
expect(valueForLetter('erudit_ru', 'A')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
+10
-10
@@ -36,7 +36,7 @@ describe('codec', () => {
|
||||
});
|
||||
|
||||
it('encodes a SubmitPlayRequest with alphabet indices (Stage 13)', () => {
|
||||
setAlphabet('english', [
|
||||
setAlphabet('scrabble_en', [
|
||||
{ index: 0, letter: 'a', value: 1 },
|
||||
{ index: 1, letter: 'b', value: 3 },
|
||||
]);
|
||||
@@ -48,7 +48,7 @@ describe('codec', () => {
|
||||
{ row: 7, col: 7, letter: 'A', blank: false },
|
||||
{ row: 7, col: 8, letter: 'B', blank: true },
|
||||
],
|
||||
'english',
|
||||
'scrabble_en',
|
||||
);
|
||||
const r = fb.SubmitPlayRequest.getRootAsSubmitPlayRequest(new ByteBuffer(buf));
|
||||
expect(r.gameId()).toBe('g1');
|
||||
@@ -95,7 +95,7 @@ describe('codec', () => {
|
||||
const seat = fb.SeatView.endSeatView(b);
|
||||
const seats = fb.GameView.createSeatsVector(b, [seat]);
|
||||
const id = b.createString('g1');
|
||||
const variant = b.createString('english');
|
||||
const variant = b.createString('scrabble_en');
|
||||
const dv = b.createString('v1');
|
||||
const status = b.createString('active');
|
||||
const er = b.createString('');
|
||||
@@ -242,7 +242,7 @@ describe('codec', () => {
|
||||
const invitees = fb.Invitation.createInviteesVector(b, [invitee]);
|
||||
|
||||
const id = b.createString('i-1');
|
||||
const variant = b.createString('english');
|
||||
const variant = b.createString('scrabble_en');
|
||||
const dropout = b.createString('remove');
|
||||
const status = b.createString('pending');
|
||||
const gid = b.createString('');
|
||||
@@ -264,7 +264,7 @@ describe('codec', () => {
|
||||
expect(inv.inviter).toEqual({ accountId: 'u-1', displayName: 'Me' });
|
||||
expect(inv.invitees).toHaveLength(1);
|
||||
expect(inv.invitees[0]).toEqual({ accountId: 'inv-1', displayName: 'Friend', seat: 1, response: 'pending' });
|
||||
expect(inv.variant).toBe('english');
|
||||
expect(inv.variant).toBe('scrabble_en');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -273,12 +273,12 @@ describe('codec', () => {
|
||||
// the whole table), so they are independent of order.
|
||||
describe('codec — alphabet on the wire (Stage 13)', () => {
|
||||
it('encodes an ExchangeRequest as alphabet indices, blank as the sentinel', () => {
|
||||
setAlphabet('english', [
|
||||
setAlphabet('scrabble_en', [
|
||||
{ index: 0, letter: 'a', value: 1 },
|
||||
{ index: 1, letter: 'b', value: 3 },
|
||||
]);
|
||||
const r = fb.ExchangeRequest.getRootAsExchangeRequest(
|
||||
new ByteBuffer(encodeExchange('g1', ['A', '?'], 'english')),
|
||||
new ByteBuffer(encodeExchange('g1', ['A', '?'], 'scrabble_en')),
|
||||
);
|
||||
expect(r.tilesLength()).toBe(2);
|
||||
expect(r.tiles(0)).toBe(0);
|
||||
@@ -286,13 +286,13 @@ describe('codec — alphabet on the wire (Stage 13)', () => {
|
||||
});
|
||||
|
||||
it('encodes a CheckWordRequest as alphabet indices', () => {
|
||||
setAlphabet('english', [
|
||||
setAlphabet('scrabble_en', [
|
||||
{ index: 0, letter: 'a', value: 1 },
|
||||
{ index: 2, letter: 'c', value: 3 },
|
||||
{ index: 19, letter: 't', value: 1 },
|
||||
]);
|
||||
const r = fb.CheckWordRequest.getRootAsCheckWordRequest(
|
||||
new ByteBuffer(encodeCheckWord('g1', 'CAT', 'english')),
|
||||
new ByteBuffer(encodeCheckWord('g1', 'CAT', 'scrabble_en')),
|
||||
);
|
||||
expect(r.wordLength()).toBe(3);
|
||||
expect([r.word(0), r.word(1), r.word(2)]).toEqual([2, 0, 19]);
|
||||
@@ -330,7 +330,7 @@ describe('codec — alphabet on the wire (Stage 13)', () => {
|
||||
fb.StateView.addAlphabet(b, alpha);
|
||||
b.finish(fb.StateView.endStateView(b));
|
||||
|
||||
// No GameView on the buffer, so decode falls back to the default variant 'english';
|
||||
// No GameView on the buffer, so decode falls back to the default variant 'scrabble_en';
|
||||
// the embedded table is cached under it and the rack [0, blank] decodes to letters.
|
||||
const sv = decodeStateView(b.asUint8Array());
|
||||
expect(sv.rack).toEqual(['A', '?']);
|
||||
|
||||
+2
-2
@@ -325,7 +325,7 @@ export function decodeProfile(buf: Uint8Array): Profile {
|
||||
export function decodeStateView(buf: Uint8Array): StateView {
|
||||
const v = fb.StateView.getRootAsStateView(new ByteBuffer(buf));
|
||||
const g = v.game();
|
||||
const variant = (g ? s(g.variant()) : 'english') as Variant;
|
||||
const variant = (g ? s(g.variant()) : 'scrabble_en') as Variant;
|
||||
// Cache the alphabet table when the server included it (a per-variant cache miss), then
|
||||
// decode the index rack to display letters with it (Stage 13).
|
||||
if (v.alphabetLength() > 0) {
|
||||
@@ -681,7 +681,7 @@ export function decodeGcg(buf: Uint8Array): GcgExport {
|
||||
function emptyGame(): GameView {
|
||||
return {
|
||||
id: '',
|
||||
variant: 'english',
|
||||
variant: 'scrabble_en',
|
||||
dictVersion: '',
|
||||
status: '',
|
||||
players: 0,
|
||||
|
||||
@@ -15,7 +15,7 @@ const seat = (s: number, accountId: string): Seat => ({
|
||||
function game(id: string, status: GameView['status'], toMove: number, lastActivityUnix: number): GameView {
|
||||
return {
|
||||
id,
|
||||
variant: 'english',
|
||||
variant: 'scrabble_en',
|
||||
dictVersion: 'v1',
|
||||
status,
|
||||
players: 2,
|
||||
|
||||
@@ -10,11 +10,11 @@ import type { Variant } from '../model';
|
||||
// "letter+value" tokens in alphabet-index order. English Latin a..z; Russian а..я incl. ё;
|
||||
// Эрудит а..я incl. ё=0.
|
||||
const SPECS: Record<Variant, string> = {
|
||||
english:
|
||||
scrabble_en:
|
||||
'a1 b3 c3 d2 e1 f4 g2 h4 i1 j8 k5 l1 m3 n1 o1 p3 q10 r1 s1 t1 u1 v4 w4 x8 y4 z10',
|
||||
russian_scrabble:
|
||||
scrabble_ru:
|
||||
'а1 б3 в1 г3 д2 е1 ё3 ж5 з5 и1 й4 к2 л2 м2 н1 о1 п2 р1 с1 т1 у2 ф10 х5 ц5 ч5 ш8 щ10 ъ10 ы4 ь3 э8 ю8 я3',
|
||||
erudit:
|
||||
erudit_ru:
|
||||
'а1 б3 в2 г3 д2 е1 ё0 ж5 з5 и1 й2 к2 л2 м2 н1 о1 п2 р2 с2 т2 у3 ф10 х5 ц10 ч5 ш10 щ10 ъ10 ы5 ь5 э10 ю10 я3',
|
||||
};
|
||||
|
||||
|
||||
@@ -61,9 +61,9 @@ function emptyLinked(): LinkResult {
|
||||
}
|
||||
|
||||
const POOL: Record<Variant, string> = {
|
||||
english: 'AAAAEEEEIIIOONNRRTTLLSSUDGBCMPFHVWYK',
|
||||
russian_scrabble: 'ОООААЕЕИИННТТСРРВЛКМДПУЯЫЬГЗБ',
|
||||
erudit: 'ОООААЕЕИИННТТСРРВЛКМДПУЯЫЬГЗБ',
|
||||
scrabble_en: 'AAAAEEEEIIIOONNRRTTLLSSUDGBCMPFHVWYK',
|
||||
scrabble_ru: 'ОООААЕЕИИННТТСРРВЛКМДПУЯЫЬГЗБ',
|
||||
erudit_ru: 'ОООААЕЕИИННТТСРРВЛКМДПУЯЫЬГЗБ',
|
||||
};
|
||||
|
||||
function draw(variant: Variant, n: number): string[] {
|
||||
|
||||
@@ -57,7 +57,7 @@ export function mockInvitations(): Invitation[] {
|
||||
id: 'inv1',
|
||||
inviter: { accountId: 'kaya', displayName: 'Kaya' },
|
||||
invitees: [{ accountId: ME, displayName: 'You', seat: 1, response: 'pending' }],
|
||||
variant: 'english',
|
||||
variant: 'scrabble_en',
|
||||
turnTimeoutSecs: 86400,
|
||||
hintsAllowed: true,
|
||||
hintsPerPlayer: 1,
|
||||
@@ -105,7 +105,7 @@ export interface MockGame {
|
||||
chat: ChatMessage[];
|
||||
}
|
||||
|
||||
// --- active game G1: english, You (seat 0) vs Ann (seat 1), your turn ---
|
||||
// --- active game G1: scrabble_en, You (seat 0) vs Ann (seat 1), your turn ---
|
||||
|
||||
const G1_MOVES: MoveRecord[] = [
|
||||
play(0, 'H', [
|
||||
@@ -135,7 +135,7 @@ function activeGame(): MockGame {
|
||||
return {
|
||||
view: {
|
||||
id: 'g1',
|
||||
variant: 'english',
|
||||
variant: 'scrabble_en',
|
||||
dictVersion: 'v1',
|
||||
status: 'active',
|
||||
players: 2,
|
||||
@@ -169,7 +169,7 @@ function finishedG2(): MockGame {
|
||||
return {
|
||||
view: {
|
||||
id: 'g2',
|
||||
variant: 'english',
|
||||
variant: 'scrabble_en',
|
||||
dictVersion: 'v1',
|
||||
status: 'finished',
|
||||
players: 2,
|
||||
@@ -204,7 +204,7 @@ function finishedG3(): MockGame {
|
||||
return {
|
||||
view: {
|
||||
id: 'g3',
|
||||
variant: 'russian_scrabble',
|
||||
variant: 'scrabble_ru',
|
||||
dictVersion: 'v1',
|
||||
status: 'finished',
|
||||
players: 2,
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
// FlatBuffers) and the mock transport speak this model, so the UI never touches
|
||||
// generated wire code directly.
|
||||
|
||||
export type Variant = 'english' | 'russian_scrabble' | 'erudit';
|
||||
export type Variant = 'scrabble_en' | 'scrabble_ru' | 'erudit_ru';
|
||||
|
||||
/** Backend game status strings. */
|
||||
export type GameStatus = 'active' | 'finished' | string;
|
||||
|
||||
+10
-10
@@ -1,13 +1,13 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { BOARD_SIZE, centre, premiumGrid } from './premiums';
|
||||
|
||||
// Premium-square geometry parity with scrabble-solver/rules/rules.go: english/russian
|
||||
// share standardBoard (centre is a double word); erudit shares the geometry but a
|
||||
// Premium-square geometry parity with scrabble-solver/rules/rules.go: scrabble_en/scrabble_ru
|
||||
// share standardBoard (centre is a double word); erudit_ru shares the geometry but a
|
||||
// non-doubling centre. Tile-value and alphabet parity moved to the Go engine test
|
||||
// (backend/internal/engine AlphabetTable) in Stage 13 — the server now owns that table.
|
||||
describe('premium layout', () => {
|
||||
it('is a 15x15 grid with TW corners', () => {
|
||||
const g = premiumGrid('english');
|
||||
const g = premiumGrid('scrabble_en');
|
||||
expect(g.length).toBe(BOARD_SIZE);
|
||||
expect(g[0].length).toBe(BOARD_SIZE);
|
||||
for (const [r, c] of [
|
||||
@@ -20,16 +20,16 @@ describe('premium layout', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('doubles the centre for standard variants but not for erudit', () => {
|
||||
expect(centre('english')).toEqual({ row: 7, col: 7 });
|
||||
expect(premiumGrid('english')[7][7]).toBe('DW');
|
||||
expect(premiumGrid('russian_scrabble')[7][7]).toBe('DW');
|
||||
expect(centre('erudit')).toEqual({ row: 7, col: 7 });
|
||||
expect(premiumGrid('erudit')[7][7]).toBe('');
|
||||
it('doubles the centre for standard variants but not for erudit_ru', () => {
|
||||
expect(centre('scrabble_en')).toEqual({ row: 7, col: 7 });
|
||||
expect(premiumGrid('scrabble_en')[7][7]).toBe('DW');
|
||||
expect(premiumGrid('scrabble_ru')[7][7]).toBe('DW');
|
||||
expect(centre('erudit_ru')).toEqual({ row: 7, col: 7 });
|
||||
expect(premiumGrid('erudit_ru')[7][7]).toBe('');
|
||||
});
|
||||
|
||||
it('keeps the standard premium counts', () => {
|
||||
const flat = premiumGrid('english').flat();
|
||||
const flat = premiumGrid('scrabble_en').flat();
|
||||
const count = (p: string) => flat.filter((x) => x === p).length;
|
||||
expect(count('TW')).toBe(8);
|
||||
expect(count('TL')).toBe(12);
|
||||
|
||||
@@ -51,7 +51,7 @@ const eruditBoard = [
|
||||
];
|
||||
|
||||
function template(variant: Variant): string[] {
|
||||
return variant === 'erudit' ? eruditBoard : standardBoard;
|
||||
return variant === 'erudit_ru' ? eruditBoard : standardBoard;
|
||||
}
|
||||
|
||||
function premiumOf(ch: string): Premium {
|
||||
|
||||
@@ -14,7 +14,7 @@ const seat = (s: number, accountId: string, score: number, isWinner = false): Se
|
||||
function game(seats: Seat[], status = 'finished', toMove = 0): GameView {
|
||||
return {
|
||||
id: 'g',
|
||||
variant: 'english',
|
||||
variant: 'scrabble_en',
|
||||
dictVersion: 'v1',
|
||||
status,
|
||||
players: seats.length,
|
||||
|
||||
@@ -9,14 +9,14 @@ describe('availableVariants', () => {
|
||||
});
|
||||
|
||||
it('offers only English for an en-only service', () => {
|
||||
expect(availableVariants(['en']).map((v) => v.id)).toEqual(['english']);
|
||||
expect(availableVariants(['en']).map((v) => v.id)).toEqual(['scrabble_en']);
|
||||
});
|
||||
|
||||
it('offers Russian and Эрудит for a ru-only service', () => {
|
||||
expect(availableVariants(['ru']).map((v) => v.id)).toEqual(['russian_scrabble', 'erudit']);
|
||||
expect(availableVariants(['ru']).map((v) => v.id)).toEqual(['scrabble_ru', 'erudit_ru']);
|
||||
});
|
||||
|
||||
it('offers every variant for a bilingual service', () => {
|
||||
expect(availableVariants(['en', 'ru']).map((v) => v.id)).toEqual(['english', 'russian_scrabble', 'erudit']);
|
||||
expect(availableVariants(['en', 'ru']).map((v) => v.id)).toEqual(['scrabble_en', 'scrabble_ru', 'erudit_ru']);
|
||||
});
|
||||
});
|
||||
|
||||
+10
-10
@@ -17,9 +17,9 @@ export interface VariantOption {
|
||||
// two never collide whatever the UI language); Erudit is localized "Erudite"/"Эрудит"
|
||||
// (Stage 17).
|
||||
export const ALL_VARIANTS: VariantOption[] = [
|
||||
{ id: 'english', label: 'new.english' },
|
||||
{ id: 'russian_scrabble', label: 'new.russian' },
|
||||
{ id: 'erudit', label: 'new.erudit' },
|
||||
{ id: 'scrabble_en', label: 'new.english' },
|
||||
{ id: 'scrabble_ru', label: 'new.russian' },
|
||||
{ id: 'erudit_ru', label: 'new.erudit' },
|
||||
];
|
||||
|
||||
// variantNameKey returns the i18n key for a variant's display name (used by the in-game
|
||||
@@ -31,22 +31,22 @@ export function variantNameKey(v: Variant): MessageKey {
|
||||
// VARIANT_RULES is the i18n key for each variant's one-line rules summary on the New Game
|
||||
// buttons (bag size, the ё rule, bonus differences), sourced from the engine rulesets.
|
||||
export const VARIANT_RULES: Record<Variant, MessageKey> = {
|
||||
english: 'new.rulesEnglish',
|
||||
russian_scrabble: 'new.rulesRussian',
|
||||
erudit: 'new.rulesErudit',
|
||||
scrabble_en: 'new.rulesEnglish',
|
||||
scrabble_ru: 'new.rulesRussian',
|
||||
erudit_ru: 'new.rulesErudit',
|
||||
};
|
||||
|
||||
// VARIANT_FLAG is the flag shown on a variant button: an emoji for the Scrabble variants;
|
||||
// Erudit uses the bundled USSR flag SVG (public/flag-ussr.svg), so its entry is empty.
|
||||
export const VARIANT_FLAG: Record<Variant, string> = {
|
||||
english: '🇺🇸',
|
||||
russian_scrabble: '🇷🇺',
|
||||
erudit: '',
|
||||
scrabble_en: '🇺🇸',
|
||||
scrabble_ru: '🇷🇺',
|
||||
erudit_ru: '',
|
||||
};
|
||||
|
||||
// VARIANT_LANGUAGE maps each variant to its game language. en -> English;
|
||||
// ru -> Russian + Эрудит.
|
||||
export const VARIANT_LANGUAGE: Record<Variant, 'en' | 'ru'> = { english: 'en', russian_scrabble: 'ru', erudit: 'ru' };
|
||||
export const VARIANT_LANGUAGE: Record<Variant, 'en' | 'ru'> = { scrabble_en: 'en', scrabble_ru: 'ru', erudit_ru: 'ru' };
|
||||
|
||||
// availableVariants gates ALL_VARIANTS by the session's supported languages. An empty
|
||||
// or absent set is ungated (a web/legacy session without a declared set), returning
|
||||
|
||||
@@ -144,9 +144,9 @@
|
||||
}
|
||||
|
||||
const variantKey: Record<string, MessageKey> = {
|
||||
english: 'new.english',
|
||||
russian_scrabble: 'new.russian',
|
||||
erudit: 'new.erudit',
|
||||
scrabble_en: 'new.english',
|
||||
scrabble_ru: 'new.russian',
|
||||
erudit_ru: 'new.erudit',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user