Community platform for sharing Unity game translation files with API for mod synchronization.
Live site: unitygametranslator.asymptomatikgames.com — browse game translations · user documentation
This README covers the technical side (stack, architecture, installation, configuration). For the user guide — what the mod does, editors, collaboration — see the documentation on the website.
- Browse translations by game, language, and popularity
- Upload translation files with automatic game detection (Steam, Epic, GOG)
- Fork translations to improve existing work
- Merge contributions — Main owners review and merge Branches
- Branch rating — Main owners rate contributor quality
- Inline editing — edit translations directly on the website with tag selection
- Live edit sessions — edit your LOCAL translation file in the browser while playing: search & replace, filters, quality bar, keyboard review; saves are hot-reloaded in-game via SSE, no account needed
- Private AI retranslation — the browser asks the mod (through the session stream) to retranslate a line with the player's own backend; no API key or LLM config is ever stored on the site
- Merge preview — visual diff between local (mod) and server translations
- Vote system to highlight quality translations
- Report system for moderation
- Profile management with GDPR data export and account deletion
- Multi-language UI (see
config/locales.phpfor the current list — the count is read from there everywhere, never written down) - Admin dashboard with analytics, user management, and moderation
| Term | Description |
|---|---|
| Main | The reference translation, owned by its creator and public on the website. |
| Branch | A contributor's improvements, linked to the Main and reviewed by its owner. One per user per UUID. |
| Fork | An independent translation (new lineage): its creator becomes Main owner, no longer linked to the original. |
Workflow:
- User A uploads → becomes Main owner
- User B downloads, improves, uploads → creates a Branch
- User A reviews Branches, rates contributors, and merges contributions
Constraints:
- One Main per UUID (first uploader wins)
- One Branch per user per UUID (updating replaces your Branch)
- Languages locked after first upload (source/target immutable)
| Tag | Name | Description |
|---|---|---|
| H | Human | Written by a human |
| V | Validated | Machine wording a human read and accepted |
| A | AI | Machine wording nobody has read yet |
| S | Skip | A human ruled that this line stays as it is — a fictional language, a proper name, text that must not change. Counts as settled, never as work left to do |
| M | Mod | Mod UI translations (internal, never counted) |
Published in full at /docs, formulas and constants included: every figure the site shows about a
translation comes from one of these, and whoever is being measured is entitled to read the measure.
| Measure | Formula | Answers | Shown to |
|---|---|---|---|
| Review stage | (H + V + S) / (H + V + S + A) |
Has a human been through it? | Everyone |
| Review rate | (H + S + c × V) / (H + V + S + A), c from 0.8 to 1.0 |
How well evidenced is that? | The author |
| Game coverage | resolved lines ÷ largest of the game's translations | How much of the game does it reach? | Everyone |
| Ordering | coverage × (0.5 + 0.5 × rate), then reception and freshness |
Which one first? | Nobody — it only sorts |
The 0-3 average this replaced answered "where does each line come from" when the question is "has anyone read this": untouched machine output scored a third of the scale, a file reviewed line by line stopped at two thirds unless its author retyped what the machine had right, and it was blind to how much of the game a file reached.
- Search translations by Steam ID, game name, or language
- Download translations with ETag caching
- Check for updates without downloading the full file
- Upload translations with gzip compression
- UUID check — detect if upload is New, Update, or Fork
- Branch listing — Main owners see all contributors
- Device Flow authentication — enter code on website to link mod
- Merge preview — mod sends local content, user resolves in browser
- Vote on translations
- Real-time sync via Server-Sent Events (SSE)
- Rate limiting per endpoint
OAuth providers: Google, GitHub, Discord, Twitch, Steam. Epic Games is wired in code but disabled on the live site (no API credentials — pending Epic developer approval); self-hosters with their own Epic credentials can enable it.
Device Flow for Unity mod: mod displays a code, user enters it at /link, mod receives API token via SSE stream.
- Framework: Laravel 12 (PHP 8.2+)
- Real-time: Node.js SSE micro-server + Redis pub/sub
- Database: SQLite (dev) / MySQL (prod)
- Auth: Laravel Socialite (5 OAuth providers)
- Frontend: Tailwind CSS 4, Alpine.js (CSP build), Chart.js, Font Awesome, Flag-icons
- Analytics: Built-in event tracking with daily aggregation
Two processes communicating via Redis:
Unity Mod ──► Laravel API (PHP) ◄──► Redis pub/sub ◄──► SSE Server (Node.js) ◄── Unity Mod
(business logic, (signaling) (real-time streaming)
auth, DB, uploads)
- Laravel handles business logic, authentication, database, uploads, merges, API
- Node.js SSE server streams real-time events to connected clients (lightweight transport layer)
- Redis pub/sub bridges the two: Laravel publishes events, SSE server forwards to clients
| Endpoint | Auth | Purpose |
|---|---|---|
GET /auth/device/:code/stream |
None | Device Flow: streams auth result |
GET /sync/stream?uuid=xxx&hash=yyy |
Bearer | Multi-device sync: streams translation updates |
GET /merge-preview/:token/stream |
Token | Merge completion notification |
GET /edit-session/:token/stream |
Token | Live edit session: browser saves and retranslate requests streamed to the mod |
GET /health |
None | Health check |
- PHP 8.2+ with
phpredisextension - Composer
- Node.js 18+
- Redis 6+
- SQLite or MySQL
composer setupHandles everything: dependencies, environment file, database migration, and asset building.
# Laravel
composer install
npm install
cp .env.example .env
php artisan key:generate
touch database/database.sqlite
php artisan migrate
npm run build
# SSE Server
cd sse-server
npm installConfigure in .env:
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
TWITCH_CLIENT_ID=
TWITCH_CLIENT_SECRET=
STEAM_API_KEY=
# Optional — provider wired in code but disabled without credentials
EPICGAMES_CLIENT_ID=
EPICGAMES_CLIENT_SECRET=| Provider | Console |
|---|---|
| Google Cloud Console | |
| GitHub | GitHub Developer Settings |
| Discord | Discord Developer Portal |
| Twitch | Twitch Developer Console |
| Steam | Steam Web API Key |
| Epic Games | Epic Games Developer Portal |
Both Laravel and the SSE server need the same Redis instance:
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379For Unix socket: set REDIS_SOCKET=/path/to/redis.sock (overrides host/port).
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Listening port |
HOST |
127.0.0.1 |
Bind address. Loopback by default — this server belongs behind a TLS-terminating reverse proxy. Set 0.0.0.0 when the proxy is on another host or in a container. |
REDIS_URL |
redis://127.0.0.1:6379 |
Redis connection (TCP) |
REDIS_SOCKET |
— | Redis Unix socket (overrides URL) |
LARAVEL_API_URL |
http://localhost:8000/api/v1 |
Laravel API for token validation |
ALLOWED_ORIGIN |
— | CORS origin |
PER_IP_LIMIT |
10 |
Max SSE connections per IP |
MAX_CONNECTIONS |
60 |
Global connection limit. An SSE connection holds one of the host's concurrent request slots for its whole life, and shared hosting grants only a few dozen per account — exceeding that budget takes down every site on the account, not just this one. Raise it on a host with room to spare. |
# Start Laravel dev server (runs server, queue, logs, and Vite)
composer dev
# Start SSE server (separate terminal)
cd sse-server
PORT=3001 REDIS_URL=redis://127.0.0.1:6379 LARAVEL_API_URL=http://localhost:8000/api/v1 node server.jscomposer test # Run tests
php artisan analytics:aggregate # Aggregate daily analytics
php artisan recalculate-hashes # Recalculate translation file hashesArabic, Chinese, Dutch, English, French, German, Hebrew, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Thai, Turkish, Vietnamese
- Unity Mod: github.com/djethino/UnityGameTranslator
- Laravel — PHP framework
- Laravel Socialite — OAuth authentication
- ioredis — Redis client for Node.js
- Tailwind CSS — Utility-first CSS
- Alpine.js — Lightweight JS framework (CSP build)
- Chart.js — Analytics charts
- Font Awesome — Icons
- Flag-icons — Language flags
Dual-licensed:
- Open Source: AGPL-3.0
- Commercial: Contact us for proprietary use
See LICENSING.md for details.