diff --git a/modules/coreI18n/src/main/key.scala b/modules/coreI18n/src/main/key.scala index c51eb0b25a7c6..5677c77c68a81 100644 --- a/modules/coreI18n/src/main/key.scala +++ b/modules/coreI18n/src/main/key.scala @@ -912,6 +912,7 @@ object I18nKey: val `opponentClock`: I18nKey = "nvui:opponentClock" val `gameStart`: I18nKey = "nvui:gameStart" val `boardCommandList`: I18nKey = "nvui:boardCommandList" + val `inputFormCommandList`: I18nKey = "nvui:inputFormCommandList" val `goToInputForm`: I18nKey = "nvui:goToInputForm" val `announceCurrentSquare`: I18nKey = "nvui:announceCurrentSquare" val `announceLastMove`: I18nKey = "nvui:announceLastMove" @@ -922,6 +923,11 @@ object I18nKey: val `moveToPieceByType`: I18nKey = "nvui:moveToPieceByType" val `moveToRank`: I18nKey = "nvui:moveToRank" val `moveToFile`: I18nKey = "nvui:moveToFile" + val `announcePieceLocations`: I18nKey = "nvui:announcePieceLocations" + val `announcePiecesOnRankOrFile`: I18nKey = "nvui:announcePiecesOnRankOrFile" + val `goToBoard`: I18nKey = "nvui:goToBoard" + val `movePiece`: I18nKey = "nvui:movePiece" + val `promotion`: I18nKey = "nvui:promotion" object oauthScope: val `newAccessToken`: I18nKey = "oauthScope:newAccessToken" diff --git a/translation/source/nvui.xml b/translation/source/nvui.xml index 2787866f5611c..995fd225e6a0d 100644 --- a/translation/source/nvui.xml +++ b/translation/source/nvui.xml @@ -6,20 +6,26 @@ Pieces Game status Last move - Move and command input form + Command input form Actions Your clock Opponent clock Game start Command list when the board has focus - Go to move and command input form. + Type these commands in the command input form. + Go to the command input form. Announce current square. Announce last move. Announce piece captured in last move. Announce possible moves for the selected piece. Announce possible captures with selected piece. Move to adjacent square left, right, up or down. - Move to a piece typing its type. Use uppercase to invert order. - Move to rank 1-8. - Move to file a-h. + Move to squares using piece names. For example: repeated k will move to every square where there is a knight. Use uppercase to invert order. + Move to rank 1 to 8. + Move to file a to h. + Announce locations of pieces. Example: p capital N for white knights, p lowercase k for black king. + Announce pieces on a rank or a file. Example: s a, s 1. + Go to the board. Default square is e-4. You can specify a square: board a-1 or b a-1 will take you to square a-1. + To move a piece, use standard algebraic notation. + To promote to anything else than a queen, use equals. For example a-8-equals-n promotes to a knight. diff --git a/ui/@types/lichess/i18n.d.ts b/ui/@types/lichess/i18n.d.ts index d4a1d5384f161..a73eb976841d5 100644 --- a/ui/@types/lichess/i18n.d.ts +++ b/ui/@types/lichess/i18n.d.ts @@ -1747,6 +1747,10 @@ interface I18n { announceLastMove: string; /** Announce piece captured in last move. */ announceLastMoveCapture: string; + /** Announce locations of pieces. Example: p capital N for white knights, p lowercase k for black king. */ + announcePieceLocations: string; + /** Announce pieces on a rank or a file. Example: s a, s 1. */ + announcePiecesOnRankOrFile: string; /** Announce possible captures with selected piece. */ announcePossibleCaptures: string; /** Announce possible moves for the selected piece. */ @@ -1761,19 +1765,25 @@ interface I18n { gameStart: string; /** Game status */ gameStatus: string; - /** Go to move and command input form. */ + /** Go to the board. Default square is e-4. You can specify a square: board a-1 or b a-1 will take you to square a-1. */ + goToBoard: string; + /** Go to the command input form. */ goToInputForm: string; - /** Move and command input form */ + /** Command input form */ inputForm: string; + /** Type these commands in the command input form. */ + inputFormCommandList: string; /** Last move */ lastMove: string; /** Move list */ moveList: string; - /** Move to file a-h. */ + /** To move a piece, use standard algebraic notation. */ + movePiece: string; + /** Move to file a to h. */ moveToFile: string; - /** Move to a piece typing its type. Use uppercase to invert order. */ + /** Move to squares using piece names. For example: repeated k will move to every square where there is a knight. Use uppercase to invert order. */ moveToPieceByType: string; - /** Move to rank 1-8. */ + /** Move to rank 1 to 8. */ moveToRank: string; /** Move to adjacent square left, right, up or down. */ moveWithArrows: string; @@ -1781,6 +1791,8 @@ interface I18n { opponentClock: string; /** Pieces */ pieces: string; + /** To promote to anything else than a queen, use equals. For example a-8-equals-n promotes to a knight. */ + promotion: string; /** Your clock */ yourClock: string; }; diff --git a/ui/analyse/src/plugins/analyse.nvui.ts b/ui/analyse/src/plugins/analyse.nvui.ts index e69f102965a5f..21b2ae69e1060 100644 --- a/ui/analyse/src/plugins/analyse.nvui.ts +++ b/ui/analyse/src/plugins/analyse.nvui.ts @@ -247,7 +247,7 @@ export function initModule(ctrl: AnalyseController): NvuiPlugin { `x: ${i18n.site.showThreat}`, ].reduce(addBreaks, []), ), - ...boardCommands(), + ...boardCommands(i18n), h('h2', 'Commands'), h( 'p', @@ -363,7 +363,7 @@ function onSubmit( type Command = 'p' | 's' | 'eval' | 'best' | 'prev' | 'next' | 'prev line' | 'next line' | 'pocket'; type InputCommand = { cmd: Command; - help: VNode; + help: VNode | string; cb: (ctrl: AnalyseController, notify: (txt: string) => void, style: MoveStyle, input: string) => void; invalid?: (ctrl: AnalyseController) => boolean; }; @@ -371,7 +371,7 @@ type InputCommand = { const inputCommands: InputCommand[] = [ { cmd: 'p', - help: commands.piece.help, + help: commands.piece.help(i18n), cb: (ctrl, notify, style, input) => notify( commands.piece.apply(input, ctrl.chessground.state.pieces, style) || @@ -380,7 +380,7 @@ const inputCommands: InputCommand[] = [ }, { cmd: 's', - help: commands.scan.help, + help: commands.scan.help(i18n), cb: (ctrl, notify, style, input) => notify( commands.scan.apply(input, ctrl.chessground.state.pieces, style) || diff --git a/ui/lib/src/nvui/chess.ts b/ui/lib/src/nvui/chess.ts index 8c451c179888d..87a9dc35250d1 100644 --- a/ui/lib/src/nvui/chess.ts +++ b/ui/lib/src/nvui/chess.ts @@ -162,14 +162,14 @@ const keysWithPiece = (pieces: Pieces, role?: Role, color?: Color): Key[] => [], ); -export function renderPieceKeys(pieces: Pieces, p: string, style: MoveStyle): string { +export function renderPieceKeys(pieces: Pieces, p: string, style: MoveStyle, i18n: I18n): string { const color: Color = p === p.toUpperCase() ? 'white' : 'black'; const role = charToRole(p)!; const keys = keysWithPiece(pieces, role, color); return `${color} ${role}: ${keys.length ? keys.map(k => renderKey(k, style)).join(', ') : i18n.site.none}`; } -export function renderPiecesOn(pieces: Pieces, rankOrFile: string, style: MoveStyle): string { +export function renderPiecesOn(pieces: Pieces, rankOrFile: string, style: MoveStyle, i18n: I18n): string { const renderedKeysWithPiece = Array.from(pieces) .sort(([key1], [key2]) => key1.localeCompare(key2)) .reduce( diff --git a/ui/lib/src/nvui/command.ts b/ui/lib/src/nvui/command.ts index 370078e3b5d34..4eb118f721d36 100644 --- a/ui/lib/src/nvui/command.ts +++ b/ui/lib/src/nvui/command.ts @@ -1,27 +1,28 @@ import { type VNode, type VNodeChildren, h } from 'snabbdom'; import { renderPieceKeys, renderPiecesOn, type MoveStyle } from './chess'; import type { Pieces } from '@lichess-org/chessground/types'; -import { noTrans } from '../snabbdom'; interface Command { - help: VNode; + help(i18n: I18n): VNode | string; apply(c: string, pieces: Pieces, style: MoveStyle): string | undefined; } type Commands = { [name: string]: Command; }; +const i18n = { site: { none: 'None' } } as I18n; + export const commands: Commands = { piece: { - help: noTrans('Read locations of a piece type. Example: p N, p k.'), + help: i18n => i18n.nvui.announcePieceLocations, apply(c: string, pieces: Pieces, style: MoveStyle): string | undefined { - return tryC(c, /^\/?p ([pnbrqk])$/i, p => renderPieceKeys(pieces, p, style)); + return tryC(c, /^\/?p ([pnbrqk])$/i, p => renderPieceKeys(pieces, p, style, i18n)); }, }, scan: { - help: noTrans('Read pieces on a rank or file. Example: s a, s 1.'), + help: i18n => i18n.nvui.announcePiecesOnRankOrFile, apply(c: string, pieces: Pieces, style: MoveStyle): string | undefined { - return tryC(c, /^\/?s ([a-h1-8])$/i, p => renderPiecesOn(pieces, p, style)); + return tryC(c, /^\/?s ([a-h1-8])$/i, p => renderPiecesOn(pieces, p, style, i18n)); }, }, }; @@ -30,7 +31,7 @@ function tryC(c: string, regex: RegExp, f: (arg: string) => A | undefined): A return c.match(regex) ? f(c.replace(regex, '$1')) : undefined; } -export const boardCommands = (): VNode[] => [ +export const boardCommands = (i18n: I18n): VNode[] => [ h('h2', i18n.nvui.boardCommandList), h('p', [ `i: ${i18n.nvui.goToInputForm}`, @@ -43,8 +44,8 @@ export const boardCommands = (): VNode[] => [ `shift+m: ${i18n.nvui.announcePossibleCaptures}`, `arrow keys: ${i18n.nvui.moveWithArrows}`, `k-q-r-b-n-p: ${i18n.nvui.moveToPieceByType}`, - `1-8: ${i18n.nvui.moveToRank}`, - `shift+1-8: ${i18n.nvui.moveToFile}`, + `1 to 8: ${i18n.nvui.moveToRank}`, + `shift+1 to 8: ${i18n.nvui.moveToFile}`, `shift+a/d: ${i18n.site.keyMoveBackwardOrForward}`, `alt+shift+a/d: ${i18n.site.cyclePreviousOrNextVariation}`, ].reduce(addBreaks, []), diff --git a/ui/lib/tests/command.test.ts b/ui/lib/tests/command.test.ts index fda32ce47ae57..31c1018814003 100644 --- a/ui/lib/tests/command.test.ts +++ b/ui/lib/tests/command.test.ts @@ -7,7 +7,6 @@ pieces.set('a1', { color: 'white', role: 'king' }); pieces.set('a2', { color: 'white', role: 'queen' }); pieces.set('b1', { color: 'white', role: 'knight' }); pieces.set('b2', { color: 'white', role: 'knight' }); -(window.i18n as any) = { site: { none: 'None' } }; test('piece command', () => { expect(commands.piece.apply('p N', pieces, 'san')).toBe('white knight: b1, b2'); diff --git a/ui/puzzle/src/plugins/puzzle.nvui.ts b/ui/puzzle/src/plugins/puzzle.nvui.ts index 5f8d1d308063c..51435309ad792 100644 --- a/ui/puzzle/src/plugins/puzzle.nvui.ts +++ b/ui/puzzle/src/plugins/puzzle.nvui.ts @@ -171,11 +171,11 @@ export function initModule() { 'Type these commands in the move input.', `v: ${i18n.site.viewTheSolution}`, 'l: Read last move.', - commands.piece.help, - commands.scan.help, + commands.piece.help(i18n), + commands.scan.help(i18n), ].reduce(addBreaks, []), ), - ...boardCommands(), + ...boardCommands(i18n), h('h2', 'Promotion'), h('p', [ 'Standard PGN notation selects the piece to promote to. Example: a8=n promotes to a knight.', diff --git a/ui/round/src/plugins/round.nvui.ts b/ui/round/src/plugins/round.nvui.ts index 2062c21e06c64..d57a51877066f 100644 --- a/ui/round/src/plugins/round.nvui.ts +++ b/ui/round/src/plugins/round.nvui.ts @@ -208,20 +208,17 @@ export function initModule(): NvuiPlugin { h('label', [noTrans('Board layout'), renderSetting(boardStyle, ctrl.redraw)]), h('h2', i18n.keyboardMove.keyboardInputCommands), h('p', [ - noTrans('Type these commands in the move input.'), + i18n.nvui.inputFormCommandList, + h('br'), + i18n.nvui.movePiece, + h('br'), + i18n.nvui.promotion, + h('br'), ...inputCommands .filter(c => !c.invalid?.(ctrl)) .flatMap(cmd => [`${cmd.cmd}${cmd.alt ? ` / ${cmd.alt}` : ''}: `, cmd.help, h('br')]), ]), - ...boardCommands(), - h('h2', noTrans('Promotion')), - h('p', [ - noTrans( - 'Standard PGN notation selects the piece to promote to. Example: a8=n promotes to a knight.', - ), - h('br'), - noTrans('Omission results in promotion to queen'), - ]), + ...boardCommands(i18n), ]); }, }; @@ -294,9 +291,7 @@ type InputCommand = { const inputCommands: InputCommand[] = [ { cmd: 'board', - help: noTrans( - 'Focus on board. Default square is e4. You can specify a square: board a1 or b a1 will take you to square a1.', - ), + help: i18n.nvui.goToBoard, cb: (_notify, _ctrl, _style, input) => { const words = input.split(' '); const file = words[1]?.charAt(0) || 'e'; @@ -314,7 +309,7 @@ const inputCommands: InputCommand[] = [ }, { cmd: 'last', - help: noTrans('Read last move.'), + help: i18n.nvui.announceLastMove, cb: notify => notify($('.lastMove').text()), alt: 'l', }, @@ -332,20 +327,20 @@ const inputCommands: InputCommand[] = [ }, { cmd: 'p', - help: commands.piece.help, + help: commands.piece.help(i18n), cb: (notify, ctrl, style, input) => notify( commands.piece.apply(input, ctrl.chessground.state.pieces, style) ?? - `Bad input: ${input}. Exptected format: ${commands.piece.help}`, + `Bad input: ${input}. Expected format: ${commands.piece.help}`, ), }, { cmd: 's', - help: commands.scan.help, + help: commands.scan.help(i18n), cb: (notify, ctrl, style, input) => notify( commands.scan.apply(input, ctrl.chessground.state.pieces, style) ?? - `Bad input: ${input}. Exptected format: ${commands.scan.help}`, + `Bad input: ${input}. Expected format: ${commands.scan.help}`, ), }, {