diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 42cb76a..c5e381d 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -285,6 +285,9 @@ jobs: mkdir -p "$conf" cp -r caddy otelcol prometheus tempo grafana "$conf"/ export SCRABBLE_CONFIG_DIR="$conf" + # App version for the About screen: the git tag if present, else the short SHA + # (the test checkout is shallow/untagged, so this is the SHA here — fine). + export APP_VERSION="$(git -C "$GITHUB_WORKSPACE" describe --tags --always 2>/dev/null || echo dev)" docker compose --ansi never build --progress plain docker compose --ansi never up -d --remove-orphans # The config-only services bind-mount the reseeded config dir. A plain `up -d` diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 66c96f3..7836d94 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -79,6 +79,7 @@ services: VITE_TELEGRAM_BOT_ID: ${VITE_TELEGRAM_BOT_ID:-} VITE_TELEGRAM_LINK: ${VITE_TELEGRAM_LINK:-} VITE_GATEWAY_URL: ${VITE_GATEWAY_URL:-} + VITE_APP_VERSION: ${APP_VERSION:-dev} restart: unless-stopped depends_on: [backend] environment: diff --git a/gateway/Dockerfile b/gateway/Dockerfile index bb0dd60..4a22bad 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -17,12 +17,15 @@ WORKDIR /ui RUN corepack enable && corepack prepare pnpm@11.0.9 --activate # Prod UI build vars (Vite reads VITE_-prefixed env at build; baked into the bundle). +# VITE_APP_VERSION carries `git describe` for the About screen (defaults to "dev"). ARG VITE_TELEGRAM_BOT_ID= ARG VITE_TELEGRAM_LINK= ARG VITE_GATEWAY_URL= +ARG VITE_APP_VERSION= ENV VITE_TELEGRAM_BOT_ID=$VITE_TELEGRAM_BOT_ID \ VITE_TELEGRAM_LINK=$VITE_TELEGRAM_LINK \ - VITE_GATEWAY_URL=$VITE_GATEWAY_URL + VITE_GATEWAY_URL=$VITE_GATEWAY_URL \ + VITE_APP_VERSION=$VITE_APP_VERSION # Install with the lockfile first (the workspace file carries pnpm's build-script # approval for esbuild), then build. Committed src/gen/ means no codegen here. diff --git a/ui/src/lib/aboutContent.ts b/ui/src/lib/aboutContent.ts new file mode 100644 index 0000000..dd43297 --- /dev/null +++ b/ui/src/lib/aboutContent.ts @@ -0,0 +1,62 @@ +// Localised "About" / landing copy, shared by the About screen and the public landing +// page (Stage 17). Kept out of the flat i18n catalog because it is structured (a heading, +// a rules link, two bulleted sections) and only used in these two long-form places. + +import type { Locale } from './i18n/index.svelte'; + +export interface AboutContent { + /** Prominent heading: "Scrabble" / "Эрудит (Скрэббл)". */ + title: string; + rulesUrl: string; + /** Text before the rules link. */ + rulesPrefix: string; + /** The rules link label. */ + rulesLink: string; + randomTitle: string; + /** The "respect the opponent's time" note (rendered with a ❗️ prefix). */ + randomRespect: string; + random: string[]; + friendsTitle: string; + friends: string[]; +} + +/** + * aboutContent returns the localised About/landing copy. hours is the auto-match move clock + * (backend game.DefaultTurnTimeout), inlined into the random-game time-limit bullet. + */ +export function aboutContent(locale: Locale, hours: number): AboutContent { + if (locale === 'ru') { + return { + title: 'Эрудит (Скрэббл)', + rulesUrl: 'https://ru.wikipedia.org/wiki/Скрэббл', + rulesPrefix: 'Основные ', + rulesLink: 'правила игры', + randomTitle: 'Случайная игра', + randomRespect: 'Уважайте личное время соперника, будьте терпеливы.', + random: [ + 'В игре двое соперников.', + 'Каждому доступна 1 подсказка в новой партии.', + `Лимит времени на ход: ${hours} ч. 00 минут.`, + 'Время отсутствия задаётся в профиле и продлевает лимит.', + ], + friendsTitle: 'Игра с друзьями', + friends: ['До 4-х участников.', 'Количество подсказок регулируется.', 'Произвольный лимит времени.'], + }; + } + return { + title: 'Scrabble', + rulesUrl: 'https://en.wikipedia.org/wiki/Scrabble', + rulesPrefix: 'Basic ', + rulesLink: 'game rules', + randomTitle: 'Random game', + randomRespect: "Respect your opponent's time, be patient.", + random: [ + 'Two opponents per game.', + 'Each player gets 1 hint per new game.', + `Move time limit: ${hours} h 00 min.`, + 'An away window set in your profile extends the limit.', + ], + friendsTitle: 'Game with friends', + friends: ['Up to 4 players.', 'The number of hints is configurable.', 'A custom time limit.'], + }; +} diff --git a/ui/src/screens/About.svelte b/ui/src/screens/About.svelte index 874f0da..786fb91 100644 --- a/ui/src/screens/About.svelte +++ b/ui/src/screens/About.svelte @@ -1,14 +1,37 @@
-

{t('app.title')}

-

{t('about.description')}

+

{c.title}

+

+ {c.rulesPrefix}{c.rulesLink}. +

+ +
+

{c.randomTitle}

+

❗️{c.randomRespect}

+
    + {#each c.random as item (item)}
  • {item}
  • {/each} +
+
+ +
+

{c.friendsTitle}

+
    + {#each c.friends as item (item)}
  • {item}
  • {/each} +
+
+

{t('about.version', { v: version })}

@@ -16,8 +39,41 @@ diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts index 78ed9ca..02e18e8 100644 --- a/ui/src/vite-env.d.ts +++ b/ui/src/vite-env.d.ts @@ -9,3 +9,6 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv; } + +/** App version string, injected by Vite's define from `git describe` at build time. */ +declare const __APP_VERSION__: string; diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 58e463f..b15496d 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -12,6 +12,12 @@ export default defineConfig(({ mode }) => ({ // Relative asset base so the one build serves under any path — the gateway maps the // Telegram Mini App to /telegram/ (the hash router is path-agnostic). base: './', + define: { + // App version shown on the About screen, injected at build time from `git describe` + // via a Docker build-arg (Stage 17). Falls back to "dev" for a plain local/mock build, + // so a missing build-arg never breaks the build. + __APP_VERSION__: JSON.stringify(process.env.VITE_APP_VERSION || 'dev'), + }, plugins: [svelte()], server: { port: 5173,