Files
Ilia Denisov 8881214213 R6(a): de-stage code, docs, READMEs; split stage6_test
Mechanical, behaviour-preserving removal of Stage N / TODO-N / phase (RN)
references from comments, doc-comments, service READMEs, the current-state docs
(ARCHITECTURE, FUNCTIONAL+_ru, TESTING, UI_DESIGN), config-file comments, and the
.fbs/.proto schema comments. PLAN.md / PRERELEASE.md / CLAUDE.md keep the stage
history.

- Rename the only stage-named identifiers: registerStage8 -> registerSocialOps,
  registerStage11 -> registerLinkOps (gateway transcode).
- Split stage6_test.go: TestEmailLoginFlow -> email_test.go,
  TestGuestAutoMatchLeavesNoStats (+ provisionGuest) -> account_test.go.
- Regenerated proto bindings (push.pb.go, telegram_grpc.pb.go) from the de-staged
  .proto comments; FB Go/TS bindings unchanged (flatc strips schema comments).

go build/vet/gofmt clean across modules; integration typecheck and pnpm check green.
2026-06-10 16:56:03 +02:00

145 lines
5.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package render turns a backend push event into a localized Telegram message with
// a Mini App deep-link. Only the out-of-app push set is rendered (your_turn, game_over,
// nudge, match_found, and the invitation / friend_request notify sub-kinds); every other
// kind returns ok=false so the connector skips it (the in-app stream still carries
// it).
package render
import (
"fmt"
"scrabble/pkg/fbs/scrabblefb"
"scrabble/platform/telegram/internal/deeplink"
)
// Message is a rendered notification: the body text, the launch-button label and
// the deep-link start parameter (empty opens the lobby).
type Message struct {
Text string
ButtonText string
StartParam string
}
// Render builds the localized message for a backend push event of the given kind
// and FlatBuffers payload, in language lang ("ru" selects Russian; anything else
// is English). It returns ok=false for a kind that is not delivered out-of-app.
func Render(kind string, payload []byte, lang string) (Message, bool) {
p := english
if lang == "ru" {
p = russian
}
switch kind {
case "your_turn":
ev := scrabblefb.GetRootAsYourTurnEvent(payload, 0)
return Message{Text: yourTurnText(ev, p), ButtonText: p.openGame, StartParam: deeplink.Game(string(ev.GameId()))}, true
case "game_over":
ev := scrabblefb.GetRootAsGameOverEvent(payload, 0)
return Message{Text: gameOverText(ev, p), ButtonText: p.openGame, StartParam: deeplink.Game(string(ev.GameId()))}, true
case "nudge":
ev := scrabblefb.GetRootAsNudgeEvent(payload, 0)
return Message{Text: p.nudge, ButtonText: p.openGame, StartParam: deeplink.Game(string(ev.GameId()))}, true
case "match_found":
ev := scrabblefb.GetRootAsMatchFoundEvent(payload, 0)
return Message{Text: p.matchFound, ButtonText: p.openGame, StartParam: deeplink.Game(string(ev.GameId()))}, true
case "notify":
ev := scrabblefb.GetRootAsNotificationEvent(payload, 0)
switch string(ev.Kind()) {
case "invitation":
return Message{Text: p.invitation, ButtonText: p.open}, true
case "friend_request":
return Message{Text: p.friendRequest, ButtonText: p.open}, true
}
}
return Message{}, false
}
// yourTurnText renders the enriched "your turn" body, voiced as the opponent who
// just moved ("{name}: my move — «WORD». Score 120:95"). It falls back to the plain phrase when
// the opponent name is missing (an older backend, or an unresolved name).
func yourTurnText(ev *scrabblefb.YourTurnEvent, p phrases) string {
name := string(ev.OpponentName())
if name == "" {
return p.yourTurn
}
switch string(ev.LastAction()) {
case "play":
if word := string(ev.LastWord()); word != "" {
return fmt.Sprintf(p.yourTurnPlay, name, word, string(ev.ScoreLine()))
}
return fmt.Sprintf(p.yourTurnMoved, name)
case "exchange":
return fmt.Sprintf(p.yourTurnExchange, name)
case "pass":
return fmt.Sprintf(p.yourTurnPass, name)
default:
return p.yourTurn
}
}
// gameOverText renders the "game over" body from the recipient's own perspective.
func gameOverText(ev *scrabblefb.GameOverEvent, p phrases) string {
score := string(ev.ScoreLine())
switch string(ev.Result()) {
case "won":
return fmt.Sprintf(p.gameOverWon, score)
case "lost":
return fmt.Sprintf(p.gameOverLost, score)
default:
return fmt.Sprintf(p.gameOverDraw, score)
}
}
// phrases is one language's message catalog. The yourTurn*/gameOver* entries are fmt format
// strings: yourTurnPlay takes (name, word, scoreLine); yourTurnExchange/Pass/Moved take (name);
// the gameOver* entries take (scoreLine).
type phrases struct {
yourTurn string
yourTurnPlay string
yourTurnExchange string
yourTurnPass string
yourTurnMoved string
gameOverWon string
gameOverLost string
gameOverDraw string
nudge string
matchFound string
invitation string
friendRequest string
openGame string
open string
}
var english = phrases{
yourTurn: "It's your turn.",
yourTurnPlay: "%s: my move — «%s». Score %s",
yourTurnExchange: "%s: swapping tiles, your turn.",
yourTurnPass: "%s: passing, your turn.",
yourTurnMoved: "%s moved, your turn.",
gameOverWon: "Game over — you won! Score %s",
gameOverLost: "Game over — you lost. Score %s",
gameOverDraw: "Game over — a draw. Score %s",
nudge: "You were nudged — it's your turn.",
matchFound: "Your game is ready.",
invitation: "You have a new game invitation.",
friendRequest: "You have a new friend request.",
openGame: "Open game",
open: "Open",
}
var russian = phrases{
yourTurn: "Ваш ход.",
yourTurnPlay: "%s: мой ход — «%s». Счёт %s",
yourTurnExchange: "%s: меняю фишки, ваш ход.",
yourTurnPass: "%s: пропускаю ход, ваш ход.",
yourTurnMoved: "%s сходил(а), ваш ход.",
gameOverWon: "Игра окончена — вы выиграли! Счёт %s",
gameOverLost: "Игра окончена — вы проиграли. Счёт %s",
gameOverDraw: "Игра окончена — ничья. Счёт %s",
nudge: "Вас поторопили — ваш ход.",
matchFound: "Игра найдена.",
invitation: "Вас пригласили в игру.",
friendRequest: "Вам пришла заявка в друзья.",
openGame: "Открыть игру",
open: "Открыть",
}