AT Mot — a bilingual (EN/FR) daily word game native to the AT Protocol.
0

Configure Feed

Select the types of activity you want to include in your feed.

Use AZERTY on-screen keyboard on the French page

The on-screen keyboard was hardcoded to QWERTY. French players type on
AZERTY, so show an AZERTY layout when the page language is French and
keep QWERTY for English.

renderKeyboard now takes the current lang and picks the row layout from
a per-language map; the language switcher already re-renders the UI, so
the keyboard flips live with no extra UI. Physical-keyboard typing was
already layout-agnostic, so only the on-screen keys change.

+52 -4
+7 -3
src/ui/keyboard.ts
··· 1 + import type { Lang } from '../config.js'; 1 2 import type { GameState } from '../state/game.js'; 2 3 import { keyboardStates } from '../state/game.js'; 3 4 import { el } from './dom.js'; 4 5 5 - const ROWS = ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM']; 6 + const LAYOUTS: Record<Lang, string[]> = { 7 + en: ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM'], 8 + fr: ['AZERTYUIOP', 'QSDFGHJKLM', 'WXCVBN'], 9 + }; 6 10 7 11 export interface KeyHandlers { 8 12 onLetter: (letter: string) => void; ··· 11 15 } 12 16 13 17 /** On-screen keyboard, colour-coded by best-known letter state. */ 14 - export function renderKeyboard(state: GameState, h: KeyHandlers): HTMLElement { 18 + export function renderKeyboard(state: GameState, lang: Lang, h: KeyHandlers): HTMLElement { 15 19 const states = keyboardStates(state); 16 20 const kb = el('div', { class: 'keyboard' }); 17 21 18 - ROWS.forEach((letters, i) => { 22 + LAYOUTS[lang].forEach((letters, i) => { 19 23 const row = el('div', { class: 'kbrow' }); 20 24 if (i === 2) { 21 25 row.append(
+1 -1
src/ui/playView.ts
··· 226 226 children.push(signInBar(ctx, t(ctx.lang).signInPrompt)); 227 227 } 228 228 children.push( 229 - renderKeyboard(state, { onLetter: typeLetter, onEnter: submit, onBackspace: backspace }), 229 + renderKeyboard(state, ctx.lang, { onLetter: typeLetter, onEnter: submit, onBackspace: backspace }), 230 230 ); 231 231 mount(main, ...children); 232 232 }
+44
tests/keyboard.test.ts
··· 1 + // @vitest-environment happy-dom 2 + import { describe, expect, it } from 'vitest'; 3 + import { renderKeyboard } from '../src/ui/keyboard.js'; 4 + import type { GameState } from '../src/state/game.js'; 5 + 6 + const NOOP = { onLetter: () => {}, onEnter: () => {}, onBackspace: () => {} }; 7 + 8 + function game(lang: 'en' | 'fr'): GameState { 9 + return { lang, puzzleNumber: 1, answer: 'CRANE', guesses: [], status: 'playing' }; 10 + } 11 + 12 + /** The letter keys in DOM order, ignoring the Enter / Backspace controls. */ 13 + function letterRows(kb: HTMLElement): string[] { 14 + return Array.from(kb.querySelectorAll('.kbrow')).map((row) => 15 + Array.from(row.querySelectorAll('.key:not(.wide)')) 16 + .map((b) => b.textContent) 17 + .join(''), 18 + ); 19 + } 20 + 21 + describe('renderKeyboard layout', () => { 22 + it('uses a QWERTY layout for English', () => { 23 + expect(letterRows(renderKeyboard(game('en'), 'en', NOOP))).toEqual([ 24 + 'QWERTYUIOP', 25 + 'ASDFGHJKL', 26 + 'ZXCVBNM', 27 + ]); 28 + }); 29 + 30 + it('uses an AZERTY layout for French', () => { 31 + expect(letterRows(renderKeyboard(game('fr'), 'fr', NOOP))).toEqual([ 32 + 'AZERTYUIOP', 33 + 'QSDFGHJKLM', 34 + 'WXCVBN', 35 + ]); 36 + }); 37 + 38 + it('covers all 26 letters in both layouts', () => { 39 + for (const lang of ['en', 'fr'] as const) { 40 + const letters = letterRows(renderKeyboard(game(lang), lang, NOOP)).join('').split('').sort(); 41 + expect(letters.join('')).toBe('ABCDEFGHIJKLMNOPQRSTUVWXYZ'); 42 + } 43 + }); 44 + });