Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
68271f0
feat(i18n): add locale configuration
support371 Jun 26, 2026
3509618
feat(i18n): add dictionary types
support371 Jun 26, 2026
2431fa1
feat(i18n): add server dictionary loader
support371 Jun 26, 2026
0b4c485
feat(i18n): add locale request helper
support371 Jun 26, 2026
568b18f
feat(i18n): add client locale provider
support371 Jun 26, 2026
fae61f0
feat(i18n): add language switcher
support371 Jun 26, 2026
938e8ad
feat(i18n): add locale preference API
support371 Jun 26, 2026
e871600
feat(i18n): add English source dictionary
support371 Jun 26, 2026
a32481b
feat(i18n): add Spanish dictionary
support371 Jun 26, 2026
56fe65e
feat(i18n): add French locale scaffold
support371 Jun 26, 2026
273b8a2
feat(i18n): complete French locale scaffold
support371 Jun 26, 2026
07490f0
feat(i18n): add German locale scaffold
support371 Jun 26, 2026
4d15c47
feat(i18n): add Arabic locale scaffold
support371 Jun 26, 2026
4f5496c
fix(i18n): merge incomplete locales with English fallback
support371 Jun 26, 2026
e97cd81
feat(i18n): apply locale and direction at root layout
support371 Jun 26, 2026
4d25d68
feat(i18n): add localized global navigation
support371 Jun 26, 2026
c9a30e6
feat(i18n): add localized global footer
support371 Jun 26, 2026
05d7911
feat(i18n): activate localized global shell
support371 Jun 26, 2026
710da92
feat(i18n): add locale parity validator
support371 Jun 26, 2026
4669899
feat(i18n): configure General Translation dictionaries
support371 Jun 26, 2026
e5d942a
ci(i18n): add General Translation workflow
support371 Jun 26, 2026
d5a9d0f
ci(i18n): validate multilingual build before deployment
support371 Jun 26, 2026
1599ce9
docs(i18n): document General Translation environment
support371 Jun 26, 2026
4902b13
fix(i18n): give each language selector a unique id
support371 Jun 26, 2026
1df7542
docs(i18n): document GLOBAL-1 website integration
support371 Jun 26, 2026
d827d86
feat(i18n): localize French global shell
support371 Jun 26, 2026
031ae53
feat(i18n): localize German global shell
support371 Jun 26, 2026
918dfa4
feat(i18n): localize Arabic global shell
support371 Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 10 additions & 20 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,27 @@
# =============================================================================

# --- Database ----------------------------------------------------------------
# PostgreSQL connection strings.
# POSTGRES_PRISMA_URL is the runtime/pooler connection used by the app.
# POSTGRES_URL_NON_POOLING is the direct connection used by Prisma migrations.
# In local development, both values can point to the same database.
POSTGRES_PRISMA_URL=postgresql://username:password@localhost:5432/gem_enterprise
POSTGRES_URL_NON_POOLING=postgresql://username:password@localhost:5432/gem_enterprise

# --- Authentication ----------------------------------------------------------
# Must be at least 32 characters. The app refuses to start in production
# if this is missing or set to the default development value.
# Generate with: openssl rand -hex 32
# Must be at least 32 characters. Generate with: openssl rand -hex 32
JWT_SECRET=replace-this-with-a-random-32-char-secret

# --- AI / Governed Chat ------------------------------------------------------
# Anthropic API key for the GEM Concierge chat widget.
# If absent, the widget falls back to rule-based replies (no build failure).
ANTHROPIC_API_KEY=sk-ant-replace-with-real-key

# Disclosure text shown to users before their first AI message.
# The SHA-256 hash of this exact string is stored in consent_records.
# Keep this value stable across deployments — changing it invalidates existing
# consent receipts.
NEXT_PUBLIC_AI_DISCLOSURE_TEXT=GEM Concierge is an AI assistant. It does not provide legal, financial, or investment advice. Responses are for informational purposes only. For regulated matters, speak with a qualified advisor.
ANTHROPIC_API_KEY=replace-with-anthropic-api-key
NEXT_PUBLIC_AI_DISCLOSURE_TEXT=GEM Concierge is an AI assistant. It does not replace qualified professional review.

# --- Application -------------------------------------------------------------
NEXT_PUBLIC_APP_URL=https://your-deployment-url.vercel.app
NEXT_PUBLIC_APP_URL=https://gemcybersecurityassist.com
NEXT_PUBLIC_APP_NAME=GEM Enterprise

# --- GLOBAL-1 / General Translation ------------------------------------------
# Server-side translation automation credentials. Never expose these through
# NEXT_PUBLIC_ variables or client-side code.
GT_PROJECT_ID=replace-with-general-translation-project-id
GT_API_KEY=replace-with-general-translation-api-key

# --- Email / SMTP ------------------------------------------------------------
SMTP_HOST=smtp.example.com
SMTP_PORT=587
Expand All @@ -46,11 +39,8 @@ SMTP_PASS=replace-with-smtp-password
EMAIL_FROM=GEM Enterprise <noreply@example.com>

# --- Admin Bootstrap ---------------------------------------------------------
# Used once by `npm run db:seed` to create the initial admin account.
# Change ADMIN_INITIAL_PASSWORD immediately after first login.
ADMIN_EMAIL=admin@example.com
ADMIN_INITIAL_PASSWORD=replace-with-temporary-password

# --- Audit Logging -----------------------------------------------------------
# Set to "true" to persist compliance audit events to the audit_logs table.
AUDIT_ENABLED=true
75 changes: 75 additions & 0 deletions .github/workflows/i18n-translate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Generate multilingual translations

on:
workflow_dispatch:
inputs:
base_branch:
description: Branch to translate from
required: true
default: main
type: string

permissions:
contents: write
pull-requests: write

jobs:
translate:
name: Translate dictionaries and open pull request
runs-on: ubuntu-latest
env:
GT_API_KEY: ${{ secrets.GT_API_KEY }}
GT_PROJECT_ID: ${{ secrets.GT_PROJECT_ID }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.base_branch }}
fetch-depth: 0

- uses: pnpm/action-setup@v4
with:
version: 9

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: pnpm

- name: Verify translation credentials
run: |
test -n "$GT_API_KEY" || (echo "GT_API_KEY is not configured" && exit 1)
test -n "$GT_PROJECT_ID" || (echo "GT_PROJECT_ID is not configured" && exit 1)

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Generate translations
run: npx gt@latest translate

- name: Validate dictionaries
env:
I18N_STRICT: 'true'
run: pnpm exec tsx scripts/validate-i18n.ts

- name: Open translation pull request
shell: bash
run: |
if git diff --quiet -- src/i18n/dictionaries; then
echo "No translation changes were generated."
exit 0
fi

BRANCH="i18n/general-translation-${GITHUB_RUN_ID}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$BRANCH"
git add src/i18n/dictionaries
git commit -m "i18n: refresh GLOBAL-1 translations"
git push --set-upstream origin "$BRANCH"

gh pr create \
--base "${{ inputs.base_branch }}" \
--head "$BRANCH" \
--title "i18n: refresh GLOBAL-1 translations" \
--body "Automated General Translation update. Review language quality, RTL rendering, and the preview deployment before merging."
52 changes: 47 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Deploy to Vercel Production
name: Validate and deploy GEM Enterprise

on:
pull_request:
branches:
- main
push:
branches:
- main
Expand All @@ -10,8 +13,8 @@ permissions:
contents: read

jobs:
deploy:
name: Deploy to Vercel
validate:
name: Validate application
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -34,8 +37,47 @@ jobs:
POSTGRES_PRISMA_URL: postgresql://ci:ci@localhost/gem
POSTGRES_URL_NON_POOLING: postgresql://ci:ci@localhost/gem

- name: Deploy to Vercel Production
run: npx vercel --prod --token $VERCEL_TOKEN --yes
- name: Validate multilingual dictionaries
env:
I18N_STRICT: 'true'
run: pnpm exec tsx scripts/validate-i18n.ts

- name: Lint
run: pnpm run lint

- name: Test
run: pnpm run test

- name: Build
run: pnpm run build
env:
POSTGRES_PRISMA_URL: postgresql://ci:ci@localhost/gem
POSTGRES_URL_NON_POOLING: postgresql://ci:ci@localhost/gem
JWT_SECRET: ci-only-secret-at-least-32-characters-long
NEXT_PUBLIC_APP_URL: http://localhost:3000

deploy:
name: Deploy to Vercel production
needs: validate
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 9

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Deploy to Vercel production
run: npx vercel --prod --token "$VERCEL_TOKEN" --yes
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
Expand Down
127 changes: 127 additions & 0 deletions docs/MULTILINGUAL_INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# GEM Enterprise GLOBAL-1 Multilingual Integration

## Purpose

This subsystem adds multilingual delivery to the existing GEM Enterprise Next.js application without replacing its authentication, KYC, admin, portal, or public route structure.

The first implementation layer localizes the global website shell and establishes the translation workflow. Page content can be migrated into the same dictionaries incrementally.

## Supported locales

- English (`en`) — source language
- Spanish (`es`)
- French (`fr`)
- German (`de`)
- Arabic (`ar`) — right-to-left

## Runtime flow

1. The server reads the `gem-locale` cookie.
2. If the cookie is absent, it checks the browser `Accept-Language` header.
3. English is used as the final fallback.
4. The root layout sets the document `lang` and `dir` attributes.
5. `I18nProvider` supplies the active dictionary to client components.
6. The global navigation and footer read translated labels from that dictionary.
7. The language selector posts a validated locale to `/api/locale` and refreshes the current route.

This approach preserves the existing URLs. It does not insert a locale prefix into protected routes, so authentication redirects and portal links remain stable.

## Files

- `src/i18n/config.ts` — locale list, cookie settings, browser matching, RTL direction
- `src/i18n/server.ts` — request locale resolution
- `src/i18n/get-dictionary.ts` — server dictionary loading with English fallback
- `src/i18n/types.ts` — dictionary types
- `src/i18n/dictionaries/*.json` — locale content
- `src/components/I18nProvider.tsx` — client locale context
- `src/components/LanguageSwitcher.tsx` — accessible locale selector
- `src/components/GlobalNavigation.tsx` — localized mobile-first navigation
- `src/components/GlobalFooter.tsx` — localized footer
- `src/app/api/locale/route.ts` — validated locale preference endpoint
- `scripts/validate-i18n.ts` — parity, direction, JSON, and placeholder checks
- `gt.config.json` — General Translation configuration
- `.github/workflows/i18n-translate.yml` — controlled translation pull-request workflow

## General Translation

Configure these GitHub Actions secrets:

- `GT_PROJECT_ID`
- `GT_API_KEY`

They are server-side credentials and must never use the `NEXT_PUBLIC_` prefix.

To generate translations locally:

```bash
npx gt@latest translate --dry-run
npx gt@latest translate
I18N_STRICT=true pnpm exec tsx scripts/validate-i18n.ts
```

The source dictionary is `src/i18n/dictionaries/en.json`. General Translation writes the configured target dictionaries.

## Translation readiness

Spanish currently contains a translated global-shell seed. French, German, and Arabic include safe scaffolds and English fallback coverage until the General Translation workflow is run and reviewed.

The production workflow uses strict validation. Missing target keys block the production deployment. This prevents incomplete locale packages from silently reaching the production domain.

## Adding page content

Server component example:

```ts
import { getDictionary } from "@/i18n/get-dictionary";
import { getRequestLocale } from "@/i18n/server";

const locale = await getRequestLocale();
const dictionary = await getDictionary(locale);
```

Client component example:

```ts
import { useI18n } from "@/components/I18nProvider";

const { locale, dictionary } = useI18n();
```

Add new English source keys first. Then run the translation workflow, review the generated pull request, validate the preview deployment, and merge only when all required locales are complete.

## Protected content

Do not translate:

- Environment variable names or values
- API route paths
- Database property names
- Code identifiers
- Template placeholders
- Domain names
- GEM Enterprise
- GEM Cybersecurity & Monitoring Assist
- Alliance Trust Realty

## Deployment sequence

1. Update English source strings on a feature branch.
2. Run the `Generate multilingual translations` workflow.
3. Review the generated translation pull request.
4. Confirm strict dictionary validation passes.
5. Confirm lint, tests, and production build pass.
6. Verify the Vercel preview in all supported languages.
7. Verify Arabic direction and mobile menu behavior.
8. Merge only after approval.
9. The main workflow deploys only after validation succeeds.

## Rollback

If the multilingual shell causes a regression:

1. Revert the integration pull request.
2. Keep the source dictionaries and translation branch for diagnosis.
3. Restore the original `Navigation` and `Footer` imports in `src/app/layout.tsx`.
4. Confirm the English production route and protected-route behavior.

The original navigation and footer components remain in the repository as a rollback reference.
9 changes: 9 additions & 0 deletions gt.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"defaultLocale": "en",
"locales": ["es", "fr", "de", "ar"],
"files": {
"json": {
"include": ["src/i18n/dictionaries/[locale].json"]
}
}
}
Loading
Loading