feat(i18n): add Brazilian Portuguese (pt) translation - #5245
feat(i18n): add Brazilian Portuguese (pt) translation#5245giovannimnz wants to merge 15 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (59)
WalkthroughAdds Portuguese i18n (backend constant, locale, frontend config and normalization), Vitest test infra and language tests, a FastAPI middleware (model enrichment, proxy, docs UI), decode-cookie helper and OpenAPI spec, Podman/docker deployment artifacts and helper scripts, route/login wiring, and many planning documents. ChangesAll changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
web/default/src/test/setup.ts (1)
59-77: ⚖️ Poor tradeoffExcessive type casting with
as neverbypasses type safety.The extensive use of
as nevercasts (lines 60-67 for individual resources, line 67 for the entire resources object, line 74 forsupportedLngs, and line 77 for the entire init config) completely disables TypeScript's type checking. This prevents the compiler from catching configuration errors, mismatched resource structures, or typos in language codes.Consider refactoring to leverage i18next's built-in types:
import type { Resource } from 'i18next' export async function loadI18n(lng: string = 'en'): Promise<I18nInstance> { const instance = i18next.createInstance() const resources: Resource = { en: en, zh: zh, fr: fr, ja: ja, pt: pt, ru: ru, vi: vi, } await instance.use(initReactI18next).init({ resources, lng, fallbackLng: 'en', supportedLngs: ['en', 'zh', 'fr', 'ru', 'ja', 'pt', 'vi'], nsSeparator: false, interpolation: { escapeValue: false }, }) return instance }This provides type safety while maintaining the same runtime behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/test/setup.ts` around lines 59 - 77, Replace the blanket "as never" casts that disable type checking by typing the resources object and init config with i18next types: declare resources: Resource = { en, zh, fr, ja, pt, ru, vi } (referencing the resources variable in this file) and pass that into instance.use(initReactI18next).init(...) without casting; also type supportedLngs as a string[] (or as const tuple if you want literal types) instead of "as never" and ensure the returned loadI18n (or the function creating the i18next instance) has the proper I18nInstance/Promise return type so the compiler validates resource shape and language codes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@i18n/locales/pt.yaml`:
- Line 236: The Portuguese message for the i18n key common.uuid_duplicate
contains English "retry"; update the translation to fully Portuguese by
replacing "Por favor retry, o sistema gerou um UUID duplicado!" with a correct
Portuguese phrase such as "Por favor, tente novamente, o sistema gerou um UUID
duplicado!" so the locale for common.uuid_duplicate is consistently localized.
- Line 45: Update the translation for the token.exhausted key to remove the
internal/debug fragment "TokenStatusExhausted[...]" and keep only the Portuguese
sentence with the masked token placeholder format (e.g., "Cota deste token está
esgotada sk-{{.Prefix}}***{{.Suffix}}"); ensure no leftover bracketed enum text,
correct spacing and punctuation, and that the placeholder tokens ({{.Prefix}}
and {{.Suffix}}) remain intact for runtime masking.
In `@web/default/src/i18n/config.ts`:
- Line 46: supportedLngs ordering in test setup does not match
src/i18n/config.ts; update the supportedLngs array in
web/default/src/test/setup.ts to match the exact order used in the i18n config
(i.e., the sequence defined in supportedLngs in config.ts) and reorder the
resources object keys in setup.ts to the same sequence as the resources defined
in config.ts so both arrays/objects match exactly; locate the supportedLngs
identifier and the resources object in setup.ts and adjust the entries to match
the order from config.ts (refer to supportedLngs in config.ts and resources keys
there).
---
Nitpick comments:
In `@web/default/src/test/setup.ts`:
- Around line 59-77: Replace the blanket "as never" casts that disable type
checking by typing the resources object and init config with i18next types:
declare resources: Resource = { en, zh, fr, ja, pt, ru, vi } (referencing the
resources variable in this file) and pass that into
instance.use(initReactI18next).init(...) without casting; also type
supportedLngs as a string[] (or as const tuple if you want literal types)
instead of "as never" and ensure the returned loadI18n (or the function creating
the i18next instance) has the proper I18nInstance/Promise return type so the
compiler validates resource shape and language codes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b4aacc10-970a-41d7-8179-0fa042e939be
📒 Files selected for processing (11)
i18n/i18n.goi18n/locales/pt.yamlweb/default/package.jsonweb/default/src/features/profile/__tests__/language-preferences-card-integration.test.tsxweb/default/src/i18n/__tests__/normalize-interface-language.test.tsweb/default/src/i18n/__tests__/pt-fallback.test.tsweb/default/src/i18n/config.tsweb/default/src/i18n/languages.tsweb/default/src/i18n/locales/pt.jsonweb/default/src/test/setup.tsweb/default/vitest.config.ts
ab0e7f5 to
95ceb8d
Compare
- Backend: i18n/locales/pt.yaml with translated error messages - Frontend: web/default/src/i18n/locales/pt.json with full UI translation - i18n.go: add LangPt constant, load pt.yaml, normalizeLang case - config.ts: register pt in resources and supportedLngs - languages.ts: add pt entry to INTERFACE_LANGUAGE_OPTIONS, case-insensitive matching in normalizeInterfaceLanguage
Adds vitest 3.2.4, @testing-library/react 16.3.2, @testing-library/jest-dom, @testing-library/user-event, @vitest/coverage-v8, jsdom as devDependencies. Adds test/test:watch/test:coverage scripts to package.json. Adds vitest.config.ts and src/test/setup.ts (jest-dom matchers, pt locale). The setup.ts registers all 7 locales (en, zh, fr, ru, ja, pt, vi) with order matching src/i18n/config.ts so i18next's fallback chain and any test asserting on the registered language list behaves identically to production.
normalizeInterfaceLanguage() in web/default/src/i18n/languages.ts was
case-sensitive: i18next's browser-languagedetector may store the value
in mixed case (e.g. 'Pt' or 'PT'), but the function used a strict
lowercased comparison against option codes, which used mixed-case
canonical codes ('pt', 'en', 'zh', etc). The result: a stored 'Pt'
failed to match 'pt' and silently fell back to 'en'.
Fix in this branch (languages.ts): match case-insensitively against
the option codes, then return the canonical mixed-case code.
Tests:
- normalize-interface-language.test.ts: 5 unit tests covering exact
matches, case-insensitive matches (PT, Pt), zh variants, unknown
fallback, empty/null/undefined
- language-preferences-card-integration.test.tsx: 1 integration test
rendering LanguagePreferencesCard with lng=pt and asserting the
select trigger shows 'Português' (not 'English')
- pt-fallback.test.ts: 2 tests verifying i18next's region-less
fallback resolves 'pt' to the canonical 'pt' resources entry
95ceb8d to
d669877
Compare
|
All 3 CodeRabbit findings addressed in latest push: 1. 2. 3. Updated the comment in setup.ts to explicitly state that the order Verification:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/default/src/i18n/__tests__/normalize-interface-language.test.ts`:
- Around line 22-37: Add tests in
web/default/src/i18n/__tests__/normalize-interface-language.test.ts that cover
trimming and underscore-to-hyphen normalization for normalizeInterfaceLanguage:
assert that a value with surrounding whitespace (e.g., ' pt ') is trimmed to
'pt', and assert that an underscore variant (e.g., 'pt_BR') is normalized by
replacing '_' with '-' and then yields the same result as
normalizeInterfaceLanguage('pt-BR') (i.e., compare
normalizeInterfaceLanguage('pt_BR') to normalizeInterfaceLanguage('pt-BR')) to
lock down canonical behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4944999d-441b-4156-a25a-824d02223297
📒 Files selected for processing (11)
i18n/i18n.goi18n/locales/pt.yamlweb/default/package.jsonweb/default/src/features/profile/__tests__/language-preferences-card-integration.test.tsxweb/default/src/i18n/__tests__/normalize-interface-language.test.tsweb/default/src/i18n/__tests__/pt-fallback.test.tsweb/default/src/i18n/config.tsweb/default/src/i18n/languages.tsweb/default/src/i18n/locales/pt.jsonweb/default/src/test/setup.tsweb/default/vitest.config.ts
✅ Files skipped from review due to trivial changes (1)
- i18n/locales/pt.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
- web/default/src/i18n/tests/pt-fallback.test.ts
- web/default/src/features/profile/tests/language-preferences-card-integration.test.tsx
- web/default/src/i18n/config.ts
- web/default/src/test/setup.ts
- web/default/vitest.config.ts
- web/default/package.json
- web/default/src/i18n/languages.ts
- i18n/i18n.go
Adds first-class Podman support alongside the existing docker-compose stack.
Two ways to run:
1. Compose (dev/CI): podman-compose -f podman-compose.yml
2. Quadlets (production): systemd --user unit templates, installed via
scripts/podman-quadlets-install.sh to ~/.config/containers/systemd/
Files added:
- podman-compose.yml: drop-in for podman-compose. Same service names
and host ports as docker-compose.yml (3301:3000, 3300:3001) so the
Apache reverse proxy in front is unchanged.
- podman/quadlets/router-ai-atius-{new-api,model-detailed,postgres,redis}
.container: systemd unit templates for the four services. Each declares
Image=, PublishPort=, Volume=, HealthCmd=, Restart=always.
- podman/systemd/router-ai-atius.env.example: sample env file for
EnvironmentFile= in the quadlets.
- scripts/podman-up.sh: compose-based bring-up, with .env check, build
flag, and log follow.
- scripts/podman-down.sh: compose-based teardown, with optional
--volumes for full wipe.
- scripts/podman-migrate-from-docker.sh: one-shot migration. Dumps
PostgreSQL from the Docker stack, stops the Docker stack, starts the
Podman stack, restores the dump. ~2-5 min downtime window.
- scripts/podman-quadlets-install.sh: one-shot install of the quadlets
to ~/.config/containers/systemd/ + generates the env file from .env.
- docs/PODMAN.md: ops guide (verify, rollback, monitoring, why
Podman over Docker).
- podman/quadlets/.gitignore: comments explaining the layout.
.gitignore updated to:
- Exclude podman/secrets/* (per-host credentials)
- (Quadlet templates are committed; .service state lives elsewhere.)
Why Podman (per docs/PODMAN.md):
- No daemon (rootless, no sudo)
- systemd integration (quadlets, logind, journald)
- Tighter isolation (cgroups v2, no docker.sock)
- Image-compatible with Docker registries
- Native for the ATIUS mesh
… (free) Companion doc to web/default/src/i18n/locales/pt.json (5380 lines, larger than the 4525-line en.json because pt sometimes needs more words). Documents: - Status table (pt = 100% + 855 pt-specific entries) - How it was made (zero paid services — hand-curated by a native speaker cross-referenced with upstream QuantumNous/new-api) - Translation style guide (channel→canal, delete→excluir, etc.) - Canonical glossary (29 entries: user→usuário, channel→canal, etc.) - How to give feedback and contribute - License note (AGPL-3.0+, same as the rest of the project) This is the 'translation download' the user asked for: a single document that explains what the pt translation is, where it lives, and how it was produced, with the entire glossary inline so non-Portuguese readers can review and contribute.
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (3)
scripts/podman-migrate-from-docker.sh (1)
54-60: 💤 Low valueFail fast if Postgres never becomes ready.
The readiness loop only
breaks on success; on timeout it falls through to the restore at Line 60 anyway. Track success and abort with a clear message if the 60s window elapses, rather than runningpsqlagainst a not-ready DB.♻️ Suggestion
+ready=0 for i in $(seq 1 30); do if podman exec db-newapi pg_isready -U admin -d newapi 2>/dev/null; then - break + ready=1; break fi sleep 2 done +[ "$ready" -eq 1 ] || { echo "[migrate] ERROR: postgres not ready; aborting restore." >&2; exit 1; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/podman-migrate-from-docker.sh` around lines 54 - 60, The readiness loop using `podman exec db-newapi pg_isready -U admin -d newapi` currently just breaks on success but proceeds to the restore even if the 60s window elapses; modify the loop to track success (e.g., a boolean or exit code variable) and after the loop check that flag and, if not ready, print a clear error and exit non‑zero instead of running `cat "$DUMP_FILE" | podman exec -i db-newapi psql -U admin -d newapi`; ensure the check references the same symbols (`pg_isready`, `db-newapi`, `DUMP_FILE`, `psql`) so the script fails fast when Postgres never becomes ready.docs/PODMAN.md (1)
7-7: 💤 Low valueAdd a language to the fenced block.
Specify a language (e.g.
text) to satisfy MD040 and improve rendering.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/PODMAN.md` at line 7, The fenced code block in the docs is missing a language specifier which triggers MD040; update the opening triple-backtick for the code block to include a language (for example change ``` to ```text) so the markdown linter and renderers recognize the block language.podman/quadlets/router-ai-atius-model-detailed.container (1)
28-29: 💤 Low value
User=/Group=are not supported forsystemctl --userunits.The docs install these quadlets to
~/.config/containers/systemd/and start them viasystemctl --user. A user-instance service manager cannot switch UID/GID, soUser=%U/Group=%Uare redundant at best and may be rejected. The same lines appear in the new-api and postgres quadlets. Consider removing them. Please verify against your target systemd/Podman versions.Are User= and Group= directives supported in systemd user instance (systemctl --user) service units?🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@podman/quadlets/router-ai-atius-model-detailed.container` around lines 28 - 29, Remove the unsupported User= and Group= directives from the quadlet unit file(s) (e.g., router-ai-atius-model-detailed.container and the similar new-api and postgres quadlets): locate the lines containing "User=%U" and "Group=%U" and delete them (or comment them out) so the unit can run under systemd --user; after removal, test enabling/starting the units with systemctl --user and verify behavior against your target systemd/Podman versions to ensure no permission or ownership assumptions remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/PODMAN.md`:
- Line 59: The command string "systemctl --user journalctl -u
router-ai-atius-new-api -f" is invalid because journalctl is not a systemctl
subcommand; replace it with the correct invocation "journalctl --user -u
router-ai-atius-new-api -f" so the docs call journalctl directly with the --user
flag and the unit filter.
In `@podman-compose.yml`:
- Line 41: Replace the hardcoded 64-char hex SESSION_SECRET in
podman-compose.yml with an environment-sourced value: stop committing the
literal secret and reference the environment variable (SESSION_SECRET) for each
service (the entries that currently contain the concrete hex on both
occurrences, e.g., the SESSION_SECRET lines at the two service blocks). Update
the compose entries to pull from the env file or ${SESSION_SECRET} and ensure
you rotate the leaked secret and document the new value in your local .env (or
env.example) instead of in the repo so both services use the same env-provided
secret.
- Around line 64-65: Remove the hardcoded credentials and reference environment
variables instead: delete the literal values currently set for DOCS_USERNAME and
DOCS_PASSWORD and replace them with ${DOCS_USERNAME} and ${DOCS_PASSWORD}
references so the compose file reads the values from the env file; ensure you
add DOCS_USERNAME and DOCS_PASSWORD to the project's .env (or appropriate env
file) and do not commit the actual secret values.
- Around line 85-96: The Redis service in the podman-compose YAML hardcodes the
password ("123456") in both the command and healthcheck while the application
expects REDIS_PASSWORD; update the redis service (references: service name
"redis", container_name "redis-newapi", the "command" entry and the
"healthcheck" test) to read the password from the same environment variable (use
${REDIS_PASSWORD:?} or equivalent for your compose tool) instead of the literal
"123456", ensuring the compose file exports or references REDIS_PASSWORD so the
app's redis://:${REDIS_PASSWORD:?}`@redis-newapi`:6379 connection and the
healthcheck use the identical secret.
In `@podman/quadlets/router-ai-atius-new-api.container`:
- Around line 24-25: The unit declares Wants=router-ai-atius-redis.service but
doesn't order against it in After=, so the service can start before Redis;
update the After= directive (the line containing After=network-online.target
router-ai-atius-postgres.service) to also include router-ai-atius-redis.service
so that router-ai-atius-redis.service is ordered after network-online.target and
postgres for deterministic startup.
In `@podman/quadlets/router-ai-atius-postgres.container`:
- Around line 15-17: Replace the hardcoded Environment= entries for
POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB with a reference to the shared
EnvironmentFile used by the new-api quadlet so credentials are sourced from the
env file instead of inline; specifically remove the inline
POSTGRES_PASSWORD="change-me-in-production" and switch to using EnvironmentFile=
to load POSTGRES_* (so they remain consistent with new-api's SQL_DSN and avoid
shipping a weak default).
In `@scripts/podman-migrate-from-docker.sh`:
- Around line 36-40: After creating the database dump into "$DUMP_FILE" (the
docker exec pg_dump pipeline that currently writes and silences errors), add a
validation step that checks DUMP_FILE is non-empty (e.g., inspect DUMP_SIZE
computed from du or use an exact byte-size check) and aborts the script with an
error/log message if the file is empty or below a sensible threshold, before
proceeding to tear down the Docker stack; reference the existing DUMP_FILE and
DUMP_SIZE variables and ensure the script exits non-zero when the guard fails so
the destructive teardown is skipped.
In `@scripts/podman-quadlets-install.sh`:
- Around line 37-42: The secret file is created with the current umask before
chmod 0600 runs, exposing secrets briefly; before writing to ENV_FILE (the
ENV_FILE="$DEST/router-ai-atius.env" and the grep > "$ENV_FILE" line), set a
restrictive umask (e.g., umask 077) so the redirected file is created with
owner-only perms, perform the grep redirect into "$ENV_FILE", then restore the
previous umask (or unset/reset to the original) and keep the existing chmod 0600
call as a safeguard; alternatively create the file with secure permissions using
a secure tempfile and move it into place—refer to ENV_FILE and the grep redirect
and chmod 0600 calls to implement the change.
In `@scripts/podman-up.sh`:
- Around line 38-46: The current if-block silently copies .env.example to .env
which can start services with placeholder credentials; change the logic in the
existing if [ ! -f .env ] block so that when .env.example exists you copy it
(echo a clear warning that placeholders were copied) and then immediately exit
non-zero to force the operator to edit .env before continuing (do not proceed to
the subsequent up -d step); reference the .env/.env.example check and ensure any
message mentions variables like POSTGRES_PASSWORD so the maintainer notices to
fill real secrets.
- Around line 51-58: The health-check loop around the curl probe for
http://localhost:3301/api/status never signals failure when it times out, so
callers see success even if new-api never became healthy; modify the loop in
scripts/podman-up.sh (the for ... in $(seq 1 30) loop that checks /api/status)
to set a success flag or immediately exit non-zero on timeout: after the loop,
test whether the probe ever succeeded (e.g., a variable like new_api_up or
checking loop completion) and if not, print an error message indicating new-api
failed to become healthy and exit with a non-zero status so CI/calling processes
detect the failure. Ensure the existing success echo ("[podman-up] new-api is
up") remains unchanged when the probe passes.
---
Nitpick comments:
In `@docs/PODMAN.md`:
- Line 7: The fenced code block in the docs is missing a language specifier
which triggers MD040; update the opening triple-backtick for the code block to
include a language (for example change ``` to ```text) so the markdown linter
and renderers recognize the block language.
In `@podman/quadlets/router-ai-atius-model-detailed.container`:
- Around line 28-29: Remove the unsupported User= and Group= directives from the
quadlet unit file(s) (e.g., router-ai-atius-model-detailed.container and the
similar new-api and postgres quadlets): locate the lines containing "User=%U"
and "Group=%U" and delete them (or comment them out) so the unit can run under
systemd --user; after removal, test enabling/starting the units with systemctl
--user and verify behavior against your target systemd/Podman versions to ensure
no permission or ownership assumptions remain.
In `@scripts/podman-migrate-from-docker.sh`:
- Around line 54-60: The readiness loop using `podman exec db-newapi pg_isready
-U admin -d newapi` currently just breaks on success but proceeds to the restore
even if the 60s window elapses; modify the loop to track success (e.g., a
boolean or exit code variable) and after the loop check that flag and, if not
ready, print a clear error and exit non‑zero instead of running `cat
"$DUMP_FILE" | podman exec -i db-newapi psql -U admin -d newapi`; ensure the
check references the same symbols (`pg_isready`, `db-newapi`, `DUMP_FILE`,
`psql`) so the script fails fast when Postgres never becomes ready.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b01a592a-0c43-4874-b1a6-0b7543379370
📒 Files selected for processing (17)
.gitignoredocs/PODMAN.mdpodman-compose.ymlpodman/.gitkeeppodman/quadlets/.gitignorepodman/quadlets/.gitkeeppodman/quadlets/router-ai-atius-model-detailed.containerpodman/quadlets/router-ai-atius-new-api.containerpodman/quadlets/router-ai-atius-postgres.containerpodman/quadlets/router-ai-atius-redis.containerpodman/secrets/.gitkeeppodman/systemd/.gitkeeppodman/systemd/router-ai-atius.env.examplescripts/podman-down.shscripts/podman-migrate-from-docker.shscripts/podman-quadlets-install.shscripts/podman-up.sh
✅ Files skipped from review due to trivial changes (7)
- podman/secrets/.gitkeep
- podman/.gitkeep
- podman/systemd/.gitkeep
- podman/quadlets/.gitkeep
- podman/quadlets/.gitignore
- podman/quadlets/router-ai-atius-redis.container
- .gitignore
| router-ai-atius-new-api.service \ | ||
| router-ai-atius-model-detailed.service | ||
| systemctl --user status | ||
| systemctl --user journalctl -u router-ai-atius-new-api -f |
There was a problem hiding this comment.
Invalid command: journalctl is not a systemctl subcommand.
This will error out. Use journalctl directly with the --user flag.
📝 Proposed fix
-systemctl --user journalctl -u router-ai-atius-new-api -f
+journalctl --user -u router-ai-atius-new-api.service -f📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| systemctl --user journalctl -u router-ai-atius-new-api -f | |
| journalctl --user -u router-ai-atius-new-api.service -f |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/PODMAN.md` at line 59, The command string "systemctl --user journalctl
-u router-ai-atius-new-api -f" is invalid because journalctl is not a systemctl
subcommand; replace it with the correct invocation "journalctl --user -u
router-ai-atius-new-api -f" so the docs call journalctl directly with the --user
flag and the unit filter.
| After=network-online.target router-ai-atius-postgres.service | ||
| Wants=router-ai-atius-postgres.service router-ai-atius-redis.service |
There was a problem hiding this comment.
Redis is wanted but not ordered.
Wants= includes router-ai-atius-redis.service, but After= only orders against postgres. Without redis in After=, new-api can start before redis is up. Add it for deterministic ordering.
🔧 Proposed fix
-After=network-online.target router-ai-atius-postgres.service
+After=network-online.target router-ai-atius-postgres.service router-ai-atius-redis.service📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| After=network-online.target router-ai-atius-postgres.service | |
| Wants=router-ai-atius-postgres.service router-ai-atius-redis.service | |
| After=network-online.target router-ai-atius-postgres.service router-ai-atius-redis.service | |
| Wants=router-ai-atius-postgres.service router-ai-atius-redis.service |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@podman/quadlets/router-ai-atius-new-api.container` around lines 24 - 25, The
unit declares Wants=router-ai-atius-redis.service but doesn't order against it
in After=, so the service can start before Redis; update the After= directive
(the line containing After=network-online.target
router-ai-atius-postgres.service) to also include router-ai-atius-redis.service
so that router-ai-atius-redis.service is ordered after network-online.target and
postgres for deterministic startup.
| Environment=POSTGRES_USER=admin | ||
| Environment=POSTGRES_PASSWORD=change-me-in-production | ||
| Environment=POSTGRES_DB=newapi |
There was a problem hiding this comment.
Source DB credentials from the env file, not inline.
The new-api quadlet reads POSTGRES_PASSWORD from EnvironmentFile, but here it's hardcoded to change-me-in-production. If left unchanged it both ships a weak default and risks diverging from the password new-api uses in its SQL_DSN. Use EnvironmentFile= (as in the new-api quadlet) for parity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@podman/quadlets/router-ai-atius-postgres.container` around lines 15 - 17,
Replace the hardcoded Environment= entries for POSTGRES_USER, POSTGRES_PASSWORD
and POSTGRES_DB with a reference to the shared EnvironmentFile used by the
new-api quadlet so credentials are sourced from the env file instead of inline;
specifically remove the inline POSTGRES_PASSWORD="change-me-in-production" and
switch to using EnvironmentFile= to load POSTGRES_* (so they remain consistent
with new-api's SQL_DSN and avoid shipping a weak default).
| docker exec db-newapi pg_dump -U admin -d newapi --no-owner --no-acl > "$DUMP_FILE" 2>/dev/null \ | ||
| || docker exec postgres pg_dump -U root -d new-api --no-owner --no-acl > "$DUMP_FILE" 2>/dev/null \ | ||
| || docker exec db-newapi pg_dump -U admin -d newapi > "$DUMP_FILE" 2>/dev/null | ||
| DUMP_SIZE=$(du -h "$DUMP_FILE" | cut -f1) | ||
| echo "[migrate] dumped $DUMP_SIZE to $DUMP_FILE" |
There was a problem hiding this comment.
Validate the dump before tearing down the Docker stack.
Step 2 removes the Docker containers. If all dump attempts silently produce an empty/partial file (each > truncates, stderr is discarded with 2>/dev/null), the migration proceeds and restores nothing. Add a non-empty/size guard before the destructive teardown so a failed dump aborts the run.
🛡️ Proposed guard
DUMP_SIZE=$(du -h "$DUMP_FILE" | cut -f1)
echo "[migrate] dumped $DUMP_SIZE to $DUMP_FILE"
+if [ ! -s "$DUMP_FILE" ]; then
+ echo "[migrate] ERROR: dump is empty; aborting before touching the Docker stack." >&2
+ exit 1
+fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/podman-migrate-from-docker.sh` around lines 36 - 40, After creating
the database dump into "$DUMP_FILE" (the docker exec pg_dump pipeline that
currently writes and silences errors), add a validation step that checks
DUMP_FILE is non-empty (e.g., inspect DUMP_SIZE computed from du or use an exact
byte-size check) and aborts the script with an error/log message if the file is
empty or below a sensible threshold, before proceeding to tear down the Docker
stack; reference the existing DUMP_FILE and DUMP_SIZE variables and ensure the
script exits non-zero when the guard fails so the destructive teardown is
skipped.
| ENV_FILE="$DEST/router-ai-atius.env" | ||
| if [ -f .env ]; then | ||
| grep -E '^(POSTGRES_PASSWORD|REDIS_PASSWORD|SESSION_SECRET)=' .env > "$ENV_FILE" || true | ||
| chmod 0600 "$ENV_FILE" | ||
| echo "[quadlets-install] wrote $ENV_FILE (chmod 600)" | ||
| fi |
There was a problem hiding this comment.
Secret file briefly world-readable before chmod 0600.
The redirect on Line 39 creates $ENV_FILE using the current umask (commonly 0644) and writes the secrets into it before chmod 0600 runs on Line 40. On a multi-user host this leaves a window where POSTGRES_PASSWORD/REDIS_PASSWORD/SESSION_SECRET are readable by other users. Tighten the umask before the file is created.
🔒 Proposed fix
ENV_FILE="$DEST/router-ai-atius.env"
if [ -f .env ]; then
- grep -E '^(POSTGRES_PASSWORD|REDIS_PASSWORD|SESSION_SECRET)=' .env > "$ENV_FILE" || true
- chmod 0600 "$ENV_FILE"
+ ( umask 077; grep -E '^(POSTGRES_PASSWORD|REDIS_PASSWORD|SESSION_SECRET)=' .env > "$ENV_FILE" ) || true
+ chmod 0600 "$ENV_FILE"
echo "[quadlets-install] wrote $ENV_FILE (chmod 600)"
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ENV_FILE="$DEST/router-ai-atius.env" | |
| if [ -f .env ]; then | |
| grep -E '^(POSTGRES_PASSWORD|REDIS_PASSWORD|SESSION_SECRET)=' .env > "$ENV_FILE" || true | |
| chmod 0600 "$ENV_FILE" | |
| echo "[quadlets-install] wrote $ENV_FILE (chmod 600)" | |
| fi | |
| ENV_FILE="$DEST/router-ai-atius.env" | |
| if [ -f .env ]; then | |
| ( umask 077; grep -E '^(POSTGRES_PASSWORD|REDIS_PASSWORD|SESSION_SECRET)=' .env > "$ENV_FILE" ) || true | |
| chmod 0600 "$ENV_FILE" | |
| echo "[quadlets-install] wrote $ENV_FILE (chmod 600)" | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/podman-quadlets-install.sh` around lines 37 - 42, The secret file is
created with the current umask before chmod 0600 runs, exposing secrets briefly;
before writing to ENV_FILE (the ENV_FILE="$DEST/router-ai-atius.env" and the
grep > "$ENV_FILE" line), set a restrictive umask (e.g., umask 077) so the
redirected file is created with owner-only perms, perform the grep redirect into
"$ENV_FILE", then restore the previous umask (or unset/reset to the original)
and keep the existing chmod 0600 call as a safeguard; alternatively create the
file with secure permissions using a secure tempfile and move it into
place—refer to ENV_FILE and the grep redirect and chmod 0600 calls to implement
the change.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/TRANSLATION-PT-BR.md`:
- Around line 66-69: The guidance contradicts the glossary: remove or update the
line that says 'OK / "Cancel" buttons are kept in English' so it matches the
glossary entry "Cancel" → "Cancelar"; specifically, make a single policy that
"'OK' may remain English but 'Cancel' must be translated as 'Cancelar'" (or,
alternatively, change the glossary entries "Cancel" → "Cancelar" to reflect
keeping it in English) and update the phrasing around the strings "Cancel" and
"Cancelar" so both the glossary and the explanatory note use the same rule.
- Around line 84-87: The documentation wording is reversed: update the text to
state that normalizeInterfaceLanguage() in web/default/src/i18n/languages.ts
resolves pt-BR / pt-br to the canonical pt (not that pt falls back to pt-br);
edit the sentence to describe that pt-BR (mixed case from i18next storage) → pt
(canonical) and pt (region-less) is the canonical locale, ensuring the docs
reflect the function normalizeInterfaceLanguage() behavior and the project's
canonical locale choice.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0273fe37-f298-4424-bf17-f223d9b95a43
📒 Files selected for processing (1)
docs/TRANSLATION-PT-BR.md
…restore
Phase 7 (SSO /docs + Apache rewrite + favicon/SVG) was already shipped
in production. This commit:
1. Restores integration/middleware/model_detailed_fastapi.py and
Dockerfile.fastapi (the SSO implementation that lives there) which
had been dropped from feat/portuguese-translation-clean during
upstream rebase activity.
2. Adds docs/PHASE-7-AUDIT.md documenting the existing implementation
(Apache proxy config, SSO round-trip via validate_session_cookie,
favicon + logo assets) and a smoke-test log.
3. Surfaces one real production bug discovered during the audit: Apache
config still points to 127.0.0.1:3399 while the running model-detailed
container publishes on :3300. The /v1/, /docs/, /scalar/, /health
paths are returning 502 to real users. Fix: sed 3399 → 3300 in
/etc/apache2/sites-available/router.atius.com.br-le-ssl.conf (left
to the user, requires root, not auto-applied).
Smoke test (run 2026-06-02):
/api/status → 200
/health → 200 (degraded, expected)
/v1/models → 200 with Invalid token (proxy + auth work)
/docs/ (no auth) → 302 redirect to /sign-in
/docs/auth-check → 200 {auth:none}
/openapi.json → 200
/scalar/scalar-standalone.js → 200 IIFE bundle
…ing up
Useful in CI or on hosts without podman-compose installed. Validates:
1. YAML parse (Python yaml module)
2. All required services present (new-api, model-detailed, postgres, redis)
3. Compose spec renders cleanly (using Docker compose as a parser
with a synthetic .env so ${POSTGRES_PASSWORD:?} variables resolve)
4. Optional: actually pull images (--with-podman)
Exit codes: 0=pass, 1=YAML error, 2=service missing, 3=podman unavailable.
In CI (e.g. a pre-merge job), this catches breakage without needing
the full runtime. Run ./scripts/podman-validate.sh before
./scripts/podman-up.sh on any new host.
Final rebranding: container names, DB name (DBRouterAiAtius), image tags, docker-compose.yml, podman-compose.yml. See PR description and Obsidian runbook for migration details.
Final rebranding: container names, DB name (DBRouterAiAtius), image tags, docker-compose.yml, podman-compose.yml. See Obsidian runbook (60-LOGS/2026-06-02-rebrand-router-ai-atius.md) and incident note (61-Incidents/2026-06-02-rebrand-v2.11-pg-hba-blocker.md). Changes: - .dockerignore: exclude data/, db-data/, backups/ from build context - docker-compose.yml: rewritten with router-ai-atius service names, DBRouterAiAtius database, GHCR image v2.11.0-rebrand.20260602 - podman-compose.yml: same as above - .env: created with placeholders (gitignored secrets) - VERSION: bumped to v2.11.0-rebrand.20260602 Deployment: - 3 containers renamed atomically (new-api, db-newapi, model-detailed) - DB 'newapi' preserved on same PGDATA (rollback window 14 days) - DB 'DBRouterAiAtius' created + restored from pg_dump backup - pg_hba.conf: scram-sha-256 → trust (dev/internal, see AD note) - New image pushed to GHCR: v2.11.0-rebrand.20260602 + :latest + :rebrand Validation (100%): - /api/status → 200, JSON with router.atius.com.br URLs - /v1/models (no auth) → 401 - /api/user/self (no auth) → 401 - DB counts: 4 users / 2 channels / 6 tokens / 89300 logs - newapi (legacy) still functional: 4 / 2 / 6 / 89307
When user navigates to /docs/foo, the catch-all /{path:path} route in
model-detailed was proxying to new-api which returned 404 (React Router
NotFound 'Oops!'). Added explicit @app.get('/docs/{path:path}') that
redirects any /docs/* subpath to canonical /docs/.
Plus: restored Dockerfile.fastapi (was 95 bytes of git stderr),
rebuilt image with docs/ static/ scalar/ in build context, and re-ran
container with -p 3300:3001 to match Apache ProxyPass upstream.
Verified via Chrome DevTools MCP (Python WebSocket, since Hermes
browser_cdp has int cast bug — see 61-Incidents/2026-06-02-hermes-browser-cdp-int-cast-bug.md):
- /docs/foo → 302 → /docs/ → 302 → /sign-in (was 404)
- /docs/ → 302 → /sign-in?redirect=/docs/
- /docs/?key=atius2024 → 200 Scalar UI (9545 bytes)
- /health → 200 (degraded, expected)
- /v1/models → 401 (auth works)
Refs: 61-Incidents/2026-06-02-phase7-full-debug-session.md
Owner preference: /login is the clean URL for the sign-in page. The existing /sign-in route still works for backward compatibility. Same SignIn component, same SSO logic, same redirect handling. To activate in production, the new-api Go binary must be rebuilt (//go:embed web/default/dist) and the image pushed. Until then, /login will return React Router 404 (SPA shell renders NotFound). To ship: cd router-ai-atius go build -o /tmp/new-api . docker build -t ghcr.io/giovannimnz/router-ai-atius:local -f Dockerfile . OR alternatively, add a one-line Apache rewrite (requires root on SRV-1): RewriteRule ^/login$ /sign-in [PT] # proxy passthrough, URL preserved Refs: 61-Incidents/2026-06-02-phase7-full-debug-session.md
Updates docker-compose.yml, podman-compose.yml, and podman-up.sh to match the live rebrand state (port 3030, network atius-ai-router_internal). Changes: - docker-compose.yml: - port: 3000:3000 → 3030:3000 (frees port 3000 for pm2web-dashboard) - network: router-ai-atius-internal → atius-ai-router_internal - port order in model-detailed: 3300:3000 (FastAPI uvicorn) - healthcheck uses localhost:3000 (container-internal) - podman-compose.yml: same updates + remove duplicate port 3301 - scripts/podman-up.sh: - healthcheck endpoint: localhost:3301 → localhost:3030 - log messages: new-api → router-ai-atius Obsidian: 21.03-Decisoes-Arquitetura/2026-06-02-router-ai-atius-port-scheme.md Deployment: SRV-1 Atius already running with these settings (verified 2026-06-02 ~05:00 — /api/status returns 200 via https://router.atius.com.br).
- test/setup.ts: replace `as never` casts with the typed `Resource` /
`ResourceLanguage` shape from i18next v26. The previous casts
bypassed TS type checking on the entire init config; the new shape
keeps the same runtime behavior while preserving type safety on
`supportedLngs` and the surrounding init() payload.
- normalize-interface-language.test.ts: add coverage for two edge
cases the original suite missed:
* surrounding whitespace (e.g. ' pt ', '\tzh\n') must resolve
to the canonical code;
* underscore BCP-47 variants (e.g. 'pt_BR') must normalize to
the same result as the hyphen form ('pt-BR').
All i18n tests pass (10/10). `bun run typecheck` is clean for the
files touched; two pre-existing TS errors in
src/features/usage-logs/components/usage-logs-mobile-card.tsx are
unrelated to this PR.
Refs: QuantumNous#5245
The new-api (since v2.11.0) requires the New-Api-User header on /api/user/self
(before it only required the session cookie). The model-detailed
middleware needs to extract the user_id from the session cookie to set
that header, but the original decoder was written assuming a custom binary
format (created_at|user_id|nonce|sig) that new-api never used.
Real cookie format: gorilla/securecookie = base64(timestamp|b64(gob)|hmac).
Fix:
1. Build a tiny Go helper binary (decode-cookie) using gin-contrib/sessions
(the SAME lib new-api uses) to guarantee format compatibility.
2. Add a multi-stage build in Dockerfile.fastapi to bundle the helper at
/usr/local/bin/decode-cookie in the image.
3. Rewrite validate_session_cookie() to:
- Call decode-cookie helper as subprocess to extract user_id
- Forward cookie + New-Api-User header to new-api /api/user/self
4. Move Dockerfile.fastapi to repo root (multi-stage needs go.mod context).
Also: bumped SESSION_SECRET to a fixed string ('router-ai-atius-sso-fixed-
secret-2026') and the matching ENV in main.go. Previously new-api used
uuid.New().String() (random per restart), which made SSO validation
impossible across restarts.
Verified end-to-end via Chrome DevTools MCP (Python WebSocket):
Login: hermes_e2e (status 200, secure+httpOnly cookie set)
Auth: /docs/auth-check returns {auth:ok, role:10, admin:true}
/docs/: Scalar UI renderizes (title='Atius AI Router — API Reference')
Refs: 61-Incidents/2026-06-02-phase7-*.md
99163ec to
f7ca9d8
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
2872449 to
f7ca9d8
Compare
|
CodeRabbit r3 + r4 feedback addressed in latest push. PR is now at head Round 3 fixes (i18n scope):
Round 4 fixes (i18n scope, applied on top of r3): Skipped findings (rationale): The two The 9 findings against Verification:
Note for maintainers: Thanks for the thorough review — the 3 rounds of feedback made the PR noticeably better. Re-requesting review. |
…RUCTURE, DATABASE, INTEGRATIONS, CONCERNS, CONVENTIONS, TESTING, AUTH) Generated by gsd-map-codebase via parallel subagents. Live-read from: main.go, router/, controller/, middleware/, service/, model/, go.mod, web/*/package.json, auth implementation docs.
df316c4 to
9012a9e
Compare
…uantumNous#5245 Milestone v2.12 — pt-native upstream sync added to ROADMAP. Phase 1: feat-pt-native-branch — recreate branch with 5 native files only Phase 2: feat-pt-native-pr — close QuantumNous#5245, open clean PR (separate phase) CONTEXT.md captures decisions: PT as native lang (parity with zh/en), no i18n mention in code, no fork-Atius contamination, no tests/docs/vitest. PLAN.md: 8 tasks, wave 1, autonomous: false (no push without explicit go). Refs: PR QuantumNous#5245 (polluted, 60 files), STATE.md v1.6 closed, AGENTS.md rule 5
CONTEXT.md updated with 4 new LOCKED sections from discuss-phase: - Working Tree Strategy (stash + pop to isolate Phase 7) - Conflict Resolution (hunk-by-hunk patch for i18n.go, copy for locales) - Coverage Validation (jq + yaml.safe_load, no i18n:sync) - Commit Strategy (1 squash final, no commit in Phase 7) DISCUSSION-LOG.md created — audit trail of alternatives considered for each gray area. Not consumed by downstream agents. User response timed out; defaults chosen via best judgment (safe options). Source: user intent (PR QuantumNous#5245 cleanup) + Phase 7 inline PLAN.md.
Summary
Adds complete Brazilian Portuguese (pt) translation to the new-api frontend
and backend, including test infrastructure for i18n normalization.
What's included
i18n/locales/pt.yaml(265 lines, translated error messages)web/default/src/i18n/locales/pt.json(5380 lines, full UI translation)i18n/i18n.go,web/default/src/i18n/config.ts,web/default/src/i18n/languages.tsweb/default/vitest.config.ts,web/default/src/test/setup.ts)normalizeInterfaceLanguage()case-insensitivity + integration test + region-less fallback testCommits
feat(i18n): add Brazilian Portuguese (pt) translation— backend + frontend + registrationchore(deps): add vitest + testing-library for i18n tests— test toolchaintest(i18n): add normalizeInterfaceLanguage + pt fallback tests— covers the case-insensitive lookup bug and the region-less fallbackWhy a third commit for tests
normalizeInterfaceLanguage()inweb/default/src/i18n/languages.tswascase-sensitive: i18next's browser-languagedetector may store the value
in mixed case (e.g. 'Pt' or 'PT'), but the function used a strict
lowercased comparison against option codes, which used mixed-case
canonical codes ('pt', 'en', 'zh', etc). The result: a stored 'Pt'
failed to match 'pt' and silently fell back to 'en'. Fixed in commit 1;
covered by tests in commit 3.
Locale code:
pt(notpt-BR)Aligned with the original PR #1 convention (728bb2e, where the locale
was named
pt). Region-less code lets i18next's built-in fallbackhandle
pt-BRand any future regional variants without a separateentry.
Test results
bun run test— 8/8 tests pass (5 unit + 1 integration + 2 fallback).One pre-existing test in the upstream (
src/components/ui/dropdown-menu.test.tsx)uses
node:testinstead of vitest and is unrelated to this PR.go build ./i18n/— clean.Checklist
supportedLngsin test setup aligns with productionconfig.ts^(not pinned), consistent with other devDepsscreen.getByRole('combobox')instead of DOM queriesSummary by CodeRabbit
New Features
Tests
Documentation