Complete Translation System Implementation Plan
Comprehensive step-by-step guide for implementing Transloco + Smartcat translation system across the GoGrab Space monorepo
π Complete Translation System Implementation Plan
π Executive Summary
This document provides a complete, step-by-step implementation plan for adding multi-language support to the GoGrab Space monorepo using Transloco (translation library) + Smartcat (AI-powered translation management).
Timeline Estimate: 2-3 weeks for full implementation
Team Size: 1-2 developers + AI automation
Languages: English (primary) β Spanish, Portuguese (expandable)
π― Why This Solution?
Decision Matrix
| Criteria | Transloco + Smartcat | ngx-translate | @angular/localize |
|---|---|---|---|
| Performance | βββββ (3-5KB) | βββ (15-20KB) | βββββ (2KB) |
| Runtime Switching | β Yes | β Yes | β No |
| AI Automation | βββββ Perfect | ββββ Good | βββ Limited |
| Ease of Use | βββββ Excellent | ββββ Good | βββ Complex |
| Angular 20 Support | β Full | β οΈ Maintained | β Official |
| Tree-shakable | β Yes | β No | β Yes |
| Dynamic Content | β Perfect | β Good | β οΈ Limited |
Winner: Transloco + Smartcat offers the best balance for small teams needing performance, ease of use, and AI automation.
ποΈ Architecture Overview
System Components
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TRANSLATION SYSTEM β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββ ββββββββββββββββ β
β β Transloco βββββββΊβ Smartcat AI β β
β β (Runtime) β β (Management)β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β β β β
β βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββ β
β β Translation Files (JSON) β β
β β βββ en.json (English - Primary) β β
β β βββ es.json (Spanish) β β
β β βββ pt.json (Portuguese) β β
β βββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββ β
β β Application Layer β β
β β βββ Static UI (Transloco) β β
β β βββ Dynamic Content (MongoDB) β β
β β βββ User-Generated (Space Builder) β β
β βββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Translation Layers
Static UI Translations (Transloco)
- Application interface (buttons, labels, menus)
- Error messages, tooltips, notifications
- Stored in:
libs/libs-foundational/i18n/src/assets/i18n/{lang}.json
Dynamic Platform Content (MongoDB + Transloco)
- User-created universes, pages, components
- Widget configurations, module descriptions
- Stored in: Database with multi-language schema
Documentation (Existing System)
- Already implemented in
docs-angular - Keep separate (working well)
- Already implemented in
π¦ Phase 1: Foundation Setup (Week 1, Days 1-3)
Step 1.1: Create Translation Library
Goal: Centralized NX library for all translation logic
# Create the i18n library
yarn create:lib:proper
# When prompted:
# Name: i18n
# Category: libs-foundational
# Type: Angular library
# Description: Internationalization system using Transloco
Expected Structure:
libs/libs-foundational/i18n/
βββ src/
β βββ lib/
β β βββ services/
β β β βββ translation.service.ts # Main service
β β β βββ transloco-loader.service.ts # Custom loader
β β β βββ language-detector.service.ts # Auto-detect language
β β βββ models/
β β β βββ translation.models.ts # TypeScript interfaces
β β β βββ language.types.ts # Language codes
β β βββ config/
β β β βββ transloco.config.ts # Transloco configuration
β β βββ utils/
β β βββ translation.utils.ts # Helper functions
β βββ assets/
β β βββ i18n/ # Translation files
β β βββ en.json # English (primary)
β β βββ es.json # Spanish
β β βββ pt.json # Portuguese
β βββ index.ts # Public API
βββ project.json
βββ tsconfig.lib.json
βββ README.md
Files to Create:
libs/libs-foundational/i18n/src/lib/models/translation.models.ts
/**
* Supported language codes
*/
export type LanguageCode = 'en' | 'es' | 'pt';
/**
* Language configuration
*/
export interface LanguageConfig {
code: LanguageCode;
label: string;
nativeLabel: string;
flag: string;
direction: 'ltr' | 'rtl';
}
/**
* Translation namespace for scoped translations
*/
export type TranslationNamespace =
| 'common' // Shared translations
| 'auth' // Authentication
| 'dashboard' // Dashboard
| 'editor' // Editor
| 'space-map' // Space Map
| 'singularity' // Singularity
| 'widgets' // Widgets
| 'errors' // Error messages
| 'validation'; // Form validation
/**
* Dynamic content translation structure
*/
export interface DynamicTranslation {
[key: string]: {
[lang in LanguageCode]?: string;
};
}
libs/libs-foundational/i18n/src/lib/config/transloco.config.ts
import {
provideTransloco,
TranslocoModule,
TranslocoConfig,
TRANSLOCO_SCOPE,
TRANSLOCO_MISSING_HANDLER
} from '@ngneat/transloco';
import { isDevMode } from '@angular/core';
import { TranslocoHttpLoader } from '../services/transloco-loader.service';
import { LanguageCode } from '../models/translation.models';
/**
* Transloco configuration for GoGrab Space
*/
export const translocoConfig: TranslocoConfig = {
availableLangs: [
{ id: 'en', label: 'English' },
{ id: 'es', label: 'EspaΓ±ol' },
{ id: 'pt', label: 'PortuguΓͺs' }
],
defaultLang: 'en',
fallbackLang: 'en',
reRenderOnLangChange: true,
prodMode: !isDevMode(),
flatten: {
aot: true // Flatten keys in production for better performance
},
missingHandler: {
useFallbackTranslation: true,
logMissingKey: isDevMode()
}
};
/**
* Provide Transloco configuration
* Use this in app.config.ts or standalone components
*/
export function provideTranslation() {
return [
provideTransloco({
config: translocoConfig,
loader: TranslocoHttpLoader
})
];
}
libs/libs-foundational/i18n/src/lib/services/transloco-loader.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Translation, TranslocoLoader } from '@ngneat/transloco';
import { Observable } from 'rxjs';
import { LanguageCode } from '../models/translation.models';
/**
* Custom Transloco loader for loading translation files
* Supports both assets and remote loading
*/
@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoader implements TranslocoLoader {
private http = inject(HttpClient);
/**
* Load translation file for specified language
*/
getTranslation(lang: LanguageCode): Observable<Translation> {
// Load from assets folder
return this.http.get<Translation>(`/assets/i18n/${lang}.json`);
// Alternative: Load from backend
// return this.http.get<Translation>(`/api/translations/${lang}`);
}
}
libs/libs-foundational/i18n/src/lib/services/translation.service.ts
import { Injectable, inject, signal, computed } from '@angular/core';
import { TranslocoService } from '@ngneat/transloco';
import { Observable } from 'rxjs';
import { LanguageCode, LanguageConfig } from '../models/translation.models';
/**
* Translation service wrapper for GoGrab Space
* Provides simplified API and app-specific functionality
*/
@Injectable({ providedIn: 'root' })
export class TranslationService {
private transloco = inject(TranslocoService);
// Current language signal
private currentLangSignal = signal<LanguageCode>('en');
// Available languages
readonly availableLanguages: LanguageConfig[] = [
{
code: 'en',
label: 'English',
nativeLabel: 'English',
flag: 'πΊπΈ',
direction: 'ltr'
},
{
code: 'es',
label: 'Spanish',
nativeLabel: 'EspaΓ±ol',
flag: 'πͺπΈ',
direction: 'ltr'
},
{
code: 'pt',
label: 'Portuguese',
nativeLabel: 'PortuguΓͺs',
flag: 'π΅πΉ',
direction: 'ltr'
}
];
// Current language configuration
readonly currentLanguage = computed(() =>
this.availableLanguages.find(
lang => lang.code === this.currentLangSignal()
) || this.availableLanguages[0]
);
constructor() {
// Subscribe to language changes
this.transloco.langChanges$.subscribe(lang => {
this.currentLangSignal.set(lang as LanguageCode);
this.persistLanguagePreference(lang as LanguageCode);
});
// Load saved language preference
this.loadLanguagePreference();
}
/**
* Get translation for a key
*/
translate(key: string, params?: Record<string, unknown>): string {
return this.transloco.translate(key, params);
}
/**
* Get translation as observable (reactive)
*/
translate$(key: string, params?: Record<string, unknown>): Observable<string> {
return this.transloco.selectTranslate(key, params);
}
/**
* Change application language
*/
setLanguage(lang: LanguageCode): void {
this.transloco.setActiveLang(lang);
}
/**
* Get current language code
*/
getCurrentLanguage(): LanguageCode {
return this.currentLangSignal();
}
/**
* Check if language is available
*/
isLanguageAvailable(lang: string): boolean {
return this.availableLanguages.some(l => l.code === lang);
}
/**
* Persist language preference to local storage
*/
private persistLanguagePreference(lang: LanguageCode): void {
localStorage.setItem('gg-space-language', lang);
}
/**
* Load language preference from local storage
*/
private loadLanguagePreference(): void {
const savedLang = localStorage.getItem('gg-space-language') as LanguageCode;
if (savedLang && this.isLanguageAvailable(savedLang)) {
this.setLanguage(savedLang);
} else {
// Auto-detect browser language
const browserLang = navigator.language.split('-')[0] as LanguageCode;
this.setLanguage(
this.isLanguageAvailable(browserLang) ? browserLang : 'en'
);
}
}
}
libs/libs-foundational/i18n/src/index.ts
// Public API
export * from './lib/models/translation.models';
export * from './lib/services/translation.service';
export * from './lib/services/transloco-loader.service';
export * from './lib/config/transloco.config';
Step 1.2: Install Transloco
# Install Transloco packages
npm install @ngneat/transloco --save
# Install dev tools for key extraction
npm install @ngneat/transloco-keys-manager --save-dev
Step 1.3: Configure Translation Files
Create initial translation files with basic structure:
libs/libs-foundational/i18n/src/assets/i18n/en.json
{
"common": {
"welcome": "Welcome",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"loading": "Loading...",
"error": "An error occurred"
},
"auth": {
"login": "Log In",
"logout": "Log Out",
"email": "Email",
"password": "Password",
"forgotPassword": "Forgot Password?"
},
"dashboard": {
"title": "Dashboard",
"welcome": "Welcome to GoGrab Space",
"subtitle": "Your journey to building amazing digital experiences starts here"
}
}
libs/libs-foundational/i18n/src/assets/i18n/es.json
{
"common": {
"welcome": "Bienvenido",
"save": "Guardar",
"cancel": "Cancelar",
"delete": "Eliminar",
"edit": "Editar",
"loading": "Cargando...",
"error": "OcurriΓ³ un error"
},
"auth": {
"login": "Iniciar SesiΓ³n",
"logout": "Cerrar SesiΓ³n",
"email": "Correo ElectrΓ³nico",
"password": "ContraseΓ±a",
"forgotPassword": "ΒΏOlvidaste tu ContraseΓ±a?"
},
"dashboard": {
"title": "Panel de Control",
"welcome": "Bienvenido a GoGrab Space",
"subtitle": "Tu viaje para construir experiencias digitales increΓbles comienza aquΓ"
}
}
libs/libs-foundational/i18n/src/assets/i18n/pt.json
{
"common": {
"welcome": "Bem-vindo",
"save": "Salvar",
"cancel": "Cancelar",
"delete": "Excluir",
"edit": "Editar",
"loading": "Carregando...",
"error": "Ocorreu um erro"
},
"auth": {
"login": "Entrar",
"logout": "Sair",
"email": "E-mail",
"password": "Senha",
"forgotPassword": "Esqueceu a Senha?"
},
"dashboard": {
"title": "Painel",
"welcome": "Bem-vindo ao GoGrab Space",
"subtitle": "Sua jornada para construir experiΓͺncias digitais incrΓveis comeΓ§a aqui"
}
}
Step 1.4: Update Library project.json
Ensure translation assets are copied during build:
libs/libs-foundational/i18n/project.json
{
"name": "i18n",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/libs-foundational/i18n/src",
"prefix": "gg",
"tags": ["scope:libs-foundational", "type:library"],
"projectType": "library",
"targets": {
"build": {
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/dist/libs/libs-foundational/i18n"],
"options": {
"command": "node tools/scripts/build/build-angular-library.js libs-foundational/i18n"
},
"configurations": {
"production": {
"command": "node tools/scripts/build/build-angular-library.js libs-foundational/i18n"
}
},
"defaultConfiguration": "production",
"dependsOn": ["^build"],
"cache": true,
"parallelism": true
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/libs-foundational/i18n/jest.config.ts"
}
}
}
}
Step 1.5: Build the Library
# Build the i18n library
nx build i18n --skip-nx-cache
# Verify output structure
ls -la dist/libs/libs-foundational/i18n/
# Should see: index.d.ts, index.js, assets/ folder
π Phase 2: Integration (Week 1, Days 4-5)
Step 2.1: Update App Configuration
apps/gograb-space/src/app/app.config.ts
import { ApplicationConfig, provideZoneChangeDetection, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
// Import translation configuration
import { provideTranslation } from '@libs-foundational/i18n';
import { gSpaceMainRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(gSpaceMainRoutes),
provideHttpClient(),
provideAnimations(),
// Add translation support
provideTranslation(),
// ... other providers
]
};
Step 2.2: Copy Translation Assets
Update Angular build configuration to copy translation files:
apps/gograb-space/project.json
{
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"options": {
"assets": [
"apps/gograb-space/src/favicon.ico",
"apps/gograb-space/src/assets",
{
"glob": "**/*",
"input": "libs/libs-foundational/i18n/src/assets",
"output": "assets"
}
]
}
}
}
}
Step 2.3: Add to tsconfig paths
Verify path mapping exists in tsconfig.base.json:
{
"compilerOptions": {
"paths": {
"@libs-foundational/i18n": ["libs/libs-foundational/i18n/src/index.ts"]
}
}
}
Step 2.4: Update project.json dependencies
apps/gograb-space/project.json
Add i18n library as dependency:
{
"implicitDependencies": ["i18n"]
}
π¨ Phase 3: UI Implementation (Week 2, Days 1-3)
Step 3.1: Create Language Switcher Component
apps/gograb-space/src/app/components/shared/language-switcher/language-switcher.component.ts
import { Component, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslationService, LanguageConfig } from '@libs-foundational/i18n';
@Component({
selector: 'gg-language-switcher',
standalone: true,
imports: [CommonModule],
template: `
<div class="language-switcher">
<button
class="current-language"
(click)="toggleDropdown()"
[attr.aria-label]="'Switch language'">
<span class="flag">{{ currentLanguage().flag }}</span>
<span class="label">{{ currentLanguage().nativeLabel }}</span>
<i class="fas fa-chevron-down"></i>
</button>
@if (isOpen()) {
<div class="language-dropdown">
@for (lang of availableLanguages; track lang.code) {
<button
class="language-option"
[class.active]="lang.code === currentLanguage().code"
(click)="selectLanguage(lang.code)">
<span class="flag">{{ lang.flag }}</span>
<span class="label">{{ lang.nativeLabel }}</span>
</button>
}
</div>
}
</div>
`,
styleUrl: './language-switcher.component.scss'
})
export class LanguageSwitcherComponent {
private translationService = inject(TranslationService);
isOpen = signal(false);
currentLanguage = this.translationService.currentLanguage;
availableLanguages = this.translationService.availableLanguages;
toggleDropdown(): void {
this.isOpen.update(v => !v);
}
selectLanguage(code: string): void {
this.translationService.setLanguage(code as any);
this.isOpen.set(false);
}
}
apps/gograb-space/src/app/components/shared/language-switcher/language-switcher.component.scss
.language-switcher {
position: relative;
.current-language {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--ux-surface);
border: 1px solid var(--ux-border);
border-radius: var(--ux-radius-md);
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: var(--ux-surface-hover);
border-color: var(--ux-primary);
}
.flag {
font-size: 1.25rem;
}
.label {
font-size: 0.875rem;
font-weight: 500;
color: var(--ux-text-primary);
}
i {
font-size: 0.75rem;
color: var(--ux-text-secondary);
transition: transform 0.2s ease;
}
}
.language-dropdown {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
min-width: 180px;
background: var(--ux-surface);
border: 1px solid var(--ux-border);
border-radius: var(--ux-radius-md);
box-shadow: var(--ux-shadow-lg);
z-index: 1000;
overflow: hidden;
.language-option {
width: 100%;
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: transparent;
border: none;
cursor: pointer;
transition: background 0.2s ease;
&:hover {
background: var(--ux-surface-hover);
}
&.active {
background: var(--ux-primary-alpha-10);
.label {
color: var(--ux-primary);
font-weight: 600;
}
}
.flag {
font-size: 1.25rem;
}
.label {
font-size: 0.875rem;
color: var(--ux-text-primary);
}
}
}
}
Step 3.2: Add Language Switcher to Header
Update apps/gograb-space/src/app/components/shared/header/header.component.ts
import { LanguageSwitcherComponent } from '../language-switcher/language-switcher.component';
@Component({
// ...
imports: [
// ... existing imports
LanguageSwitcherComponent
]
})
export class HeaderComponent {
// ... existing code
}
Update apps/gograb-space/src/app/components/shared/header/header.component.html
<header class="app-header">
<!-- ... existing header content ... -->
<div class="header-actions">
<!-- Add language switcher -->
<gg-language-switcher />
<!-- ... other header actions ... -->
</div>
</header>
Step 3.3: Update Existing Components
Example: Dashboard Component
Before:
<h1>Welcome to GoGrab Space</h1>
<p>Your journey to building amazing digital experiences starts here.</p>
After:
<h1 transloco="dashboard.welcome">Welcome to GoGrab Space</h1>
<p transloco="dashboard.subtitle">Your journey to building amazing digital experiences starts here.</p>
Explanation:
- Add
transloco="key.path"attribute to any element - The text inside becomes the default (fallback) value
- Transloco automatically replaces with translated text
Step 3.4: Import TranslocoModule in Components
For components using translations:
import { TranslocoModule } from '@ngneat/transloco';
@Component({
standalone: true,
imports: [
CommonModule,
TranslocoModule // Add this
]
})
export class MyComponent { }
π€ Phase 4: AI Translation Setup (Week 2, Days 4-5)
Step 4.1: Sign Up for Smartcat
- Create Account: https://www.smartcat.com/
- Choose Plan:
- Free tier: Good for initial setup (500k words/month)
- Pro: $99/month for larger projects
- Generate API Key: Account Settings β API β Generate Key
Step 4.2: Configure Smartcat CLI
# Install Smartcat CLI
npm install -g smartcat-cli
# Login to Smartcat
smartcat login
# Enter your API credentials when prompted
# API Key: [your-key]
# Account ID: [your-account-id]
Step 4.3: Create Smartcat Project
# Create new project
smartcat project create \
--name "GoGrab Space Translations" \
--source-language "en" \
--target-languages "es,pt"
# Note the project ID returned
Step 4.4: Create Translation Workflow Script
tools/scripts/translation/translate.js
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
/**
* Automated translation workflow using Smartcat
*/
class TranslationWorkflow {
constructor() {
this.sourceFile = 'libs/libs-foundational/i18n/src/assets/i18n/en.json';
this.targetLanguages = ['es', 'pt'];
this.smartcatProjectId = process.env.SMARTCAT_PROJECT_ID;
}
/**
* Extract all translation keys from source files
*/
extractKeys() {
console.log('π Extracting translation keys...');
try {
execSync('npx transloco-keys-manager extract', { stdio: 'inherit' });
console.log('β
Keys extracted successfully');
} catch (error) {
console.error('β Failed to extract keys:', error.message);
process.exit(1);
}
}
/**
* Upload source file to Smartcat
*/
uploadToSmartcat() {
console.log('β¬οΈ Uploading to Smartcat...');
try {
execSync(
`smartcat file upload \
--project-id ${this.smartcatProjectId} \
--file ${this.sourceFile}`,
{ stdio: 'inherit' }
);
console.log('β
Uploaded to Smartcat');
} catch (error) {
console.error('β Failed to upload:', error.message);
process.exit(1);
}
}
/**
* Trigger AI translation
*/
triggerTranslation() {
console.log('π€ Triggering AI translation...');
try {
execSync(
`smartcat translation start \
--project-id ${this.smartcatProjectId} \
--use-ai`,
{ stdio: 'inherit' }
);
console.log('β
AI translation started');
console.log('β³ This may take a few minutes...');
} catch (error) {
console.error('β Failed to start translation:', error.message);
process.exit(1);
}
}
/**
* Download translated files
*/
downloadTranslations() {
console.log('β¬οΈ Downloading translations...');
this.targetLanguages.forEach(lang => {
const outputFile = this.sourceFile.replace('en.json', `${lang}.json`);
try {
execSync(
`smartcat file download \
--project-id ${this.smartcatProjectId} \
--language ${lang} \
--output ${outputFile}`,
{ stdio: 'inherit' }
);
console.log(`β
Downloaded ${lang}.json`);
} catch (error) {
console.error(`β Failed to download ${lang}:`, error.message);
}
});
}
/**
* Run complete workflow
*/
async run() {
console.log('π Starting Translation Workflow\n');
// Step 1: Extract keys from code
this.extractKeys();
// Step 2: Upload to Smartcat
this.uploadToSmartcat();
// Step 3: Trigger AI translation
this.triggerTranslation();
// Step 4: Wait for translation (poll every 30 seconds)
await this.waitForTranslation();
// Step 5: Download results
this.downloadTranslations();
console.log('\n⨠Translation complete!');
console.log('π Files updated in: libs/libs-foundational/i18n/src/assets/i18n/');
}
/**
* Wait for translation to complete
*/
async waitForTranslation() {
return new Promise((resolve) => {
const checkStatus = () => {
try {
const result = execSync(
`smartcat translation status --project-id ${this.smartcatProjectId}`,
{ encoding: 'utf-8' }
);
if (result.includes('completed')) {
console.log('β
Translation completed');
resolve();
} else {
console.log('β³ Still translating... (checking again in 30s)');
setTimeout(checkStatus, 30000);
}
} catch (error) {
console.error('β Error checking status:', error.message);
setTimeout(checkStatus, 30000);
}
};
checkStatus();
});
}
}
// Run workflow
const workflow = new TranslationWorkflow();
workflow.run().catch(error => {
console.error('β Workflow failed:', error);
process.exit(1);
});
Step 4.5: Add NPM Scripts
Update package.json
{
"scripts": {
"translate:extract": "npx transloco-keys-manager extract",
"translate:upload": "node tools/scripts/translation/translate.js upload",
"translate:download": "node tools/scripts/translation/translate.js download",
"translate:auto": "node tools/scripts/translation/translate.js"
}
}
Step 4.6: Configure Transloco Keys Manager
Create transloco.config.js in workspace root
module.exports = {
rootTranslationsPath: 'libs/libs-foundational/i18n/src/assets/i18n/',
langs: ['en', 'es', 'pt'],
keysManager: {
input: [
'apps/gograb-space/src/**/*.{ts,html}',
'libs/**/*.{ts,html}'
],
output: 'libs/libs-foundational/i18n/src/assets/i18n',
fileFormat: 'json',
marker: 'transloco',
addMissingKeys: true,
emitErrorOnExtraKeys: false,
replace: false
}
};
πΎ Phase 5: Dynamic Content Translation (Week 3)
Step 5.1: Update Database Schema
Add translation support to MongoDB models:
apis/backend-api/src/modules/universes/schemas/universe.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema()
export class UniverseTranslation {
@Prop({ required: true })
language: string; // 'en', 'es', 'pt'
@Prop({ required: true })
title: string;
@Prop()
description?: string;
@Prop()
metadata?: Record<string, any>;
}
@Schema({ timestamps: true })
export class Universe extends Document {
@Prop({ required: true })
ownerId: string;
// Default language content
@Prop({ required: true })
title: string;
@Prop()
description?: string;
// Multi-language translations
@Prop({ type: [UniverseTranslation], default: [] })
translations: UniverseTranslation[];
// Helper method to get translated content
getTranslation(lang: string): Partial<Universe> {
const translation = this.translations.find(t => t.language === lang);
if (translation) {
return {
title: translation.title,
description: translation.description,
...translation.metadata
};
}
// Fallback to default
return {
title: this.title,
description: this.description
};
}
}
export const UniverseSchema = SchemaFactory.createForClass(Universe);
// Add helper method to schema
UniverseSchema.methods.getTranslation = function(lang: string) {
const translation = this.translations.find(t => t.language === lang);
if (translation) {
return {
title: translation.title,
description: translation.description,
...translation.metadata
};
}
return {
title: this.title,
description: this.description
};
};
Step 5.2: Create Translation Service
libs/libs-foundational/i18n/src/lib/services/dynamic-translation.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, map } from 'rxjs';
import { TranslationService } from './translation.service';
/**
* Service for translating dynamic user-generated content
*/
@Injectable({ providedIn: 'root' })
export class DynamicTranslationService {
private http = inject(HttpClient);
private translationService = inject(TranslationService);
/**
* Translate content using Smartcat API
*/
translateContent(params: {
text: string;
targetLanguages: string[];
sourceLanguage?: string;
}): Observable<Record<string, string>> {
const { text, targetLanguages, sourceLanguage = 'en' } = params;
return this.http.post<any>('/api/translations/translate', {
text,
sourceLanguage,
targetLanguages
}).pipe(
map(response => response.translations)
);
}
/**
* Get translated content for current language
*/
getTranslatedContent<T extends { translations?: any[] }>(
content: T,
fields: string[]
): T {
const currentLang = this.translationService.getCurrentLanguage();
if (currentLang === 'en' || !content.translations) {
return content;
}
const translation = content.translations.find(
t => t.language === currentLang
);
if (!translation) {
return content;
}
// Merge translation with original content
const translated = { ...content };
fields.forEach(field => {
if (translation[field]) {
translated[field] = translation[field];
}
});
return translated;
}
}
Step 5.3: Create Backend Translation Endpoint
apis/backend-api/src/modules/translations/translations.controller.ts
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { TranslationsService } from './translations.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@Controller('translations')
@UseGuards(JwtAuthGuard)
export class TranslationsController {
constructor(private translationsService: TranslationsService) {}
@Post('translate')
async translate(@Body() dto: {
text: string;
sourceLanguage: string;
targetLanguages: string[];
}) {
return this.translationsService.translateText(dto);
}
}
apis/backend-api/src/modules/translations/translations.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
@Injectable()
export class TranslationsService {
private smartcatApiKey: string;
private smartcatAccountId: string;
constructor(private configService: ConfigService) {
this.smartcatApiKey = this.configService.get('SMARTCAT_API_KEY');
this.smartcatAccountId = this.configService.get('SMARTCAT_ACCOUNT_ID');
}
async translateText(params: {
text: string;
sourceLanguage: string;
targetLanguages: string[];
}): Promise<{ translations: Record<string, string> }> {
const { text, sourceLanguage, targetLanguages } = params;
// Call Smartcat API
const translations: Record<string, string> = {};
for (const targetLang of targetLanguages) {
try {
const response = await axios.post(
'https://smartcat.com/api/integration/v1/translate',
{
text,
sourceLanguage,
targetLanguage: targetLang
},
{
headers: {
'Authorization': `Bearer ${this.smartcatApiKey}`,
'Content-Type': 'application/json'
}
}
);
translations[targetLang] = response.data.translation;
} catch (error) {
console.error(`Failed to translate to ${targetLang}:`, error);
translations[targetLang] = text; // Fallback to original
}
}
return { translations };
}
}
Step 5.4: Update Frontend Components
Example: Universe Creation Component
import { DynamicTranslationService } from '@libs-foundational/i18n';
@Component({...})
export class UniverseCreationComponent {
private dynamicTranslation = inject(DynamicTranslationService);
async createUniverse(data: { title: string; description: string }) {
// Translate to all supported languages
const translations = await firstValueFrom(
this.dynamicTranslation.translateContent({
text: JSON.stringify(data),
targetLanguages: ['es', 'pt']
})
);
// Save universe with translations
await this.universeService.create({
...data,
translations: [
{ language: 'es', ...JSON.parse(translations.es) },
{ language: 'pt', ...JSON.parse(translations.pt) }
]
});
}
}
π§ͺ Phase 6: Testing & Validation (Week 3)
Step 6.1: Manual Testing Checklist
- Language switcher appears in header
- Can switch between English, Spanish, Portuguese
- Selected language persists after page reload
- All static UI text translates correctly
- Dashboard shows translated content
- Form labels and buttons translate
- Error messages translate
- Created universes show in current language
- Dynamic content falls back to English if translation missing
Step 6.2: Create Translation Test Script
tools/scripts/translation/test-translations.js
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
/**
* Validate translation files
*/
class TranslationValidator {
constructor() {
this.translationsPath = 'libs/libs-foundational/i18n/src/assets/i18n';
this.languages = ['en', 'es', 'pt'];
}
/**
* Load translation file
*/
loadTranslation(lang) {
const filePath = path.join(this.translationsPath, `${lang}.json`);
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
}
/**
* Get all keys from object (nested)
*/
getAllKeys(obj, prefix = '') {
let keys = [];
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
keys = keys.concat(this.getAllKeys(value, fullKey));
} else {
keys.push(fullKey);
}
}
return keys;
}
/**
* Validate all translations
*/
validate() {
console.log('π Validating translations...\n');
const enTranslation = this.loadTranslation('en');
const enKeys = this.getAllKeys(enTranslation);
console.log(`β
English (source): ${enKeys.length} keys`);
let hasErrors = false;
// Validate each target language
this.languages.slice(1).forEach(lang => {
const translation = this.loadTranslation(lang);
const keys = this.getAllKeys(translation);
console.log(`\nπ ${lang.toUpperCase()}:`);
console.log(` Total keys: ${keys.length}`);
// Check for missing keys
const missingKeys = enKeys.filter(key => !keys.includes(key));
if (missingKeys.length > 0) {
console.log(` β οΈ Missing ${missingKeys.length} keys:`);
missingKeys.slice(0, 5).forEach(key => {
console.log(` - ${key}`);
});
if (missingKeys.length > 5) {
console.log(` ... and ${missingKeys.length - 5} more`);
}
hasErrors = true;
} else {
console.log(` β
All keys present`);
}
// Check for extra keys
const extraKeys = keys.filter(key => !enKeys.includes(key));
if (extraKeys.length > 0) {
console.log(` β οΈ Extra ${extraKeys.length} keys (not in English):`);
extraKeys.slice(0, 5).forEach(key => {
console.log(` - ${key}`);
});
hasErrors = true;
}
});
console.log('\n' + '='.repeat(50));
if (hasErrors) {
console.log('β Validation failed - please fix missing/extra keys');
process.exit(1);
} else {
console.log('β
All translations are valid!');
}
}
}
// Run validation
const validator = new TranslationValidator();
validator.validate();
Step 6.3: Add to CI/CD
Update .github/workflows/ci.yml or similar:
- name: Validate Translations
run: node tools/scripts/translation/test-translations.js
π Phase 7: Documentation (Week 3)
Step 7.1: Create Translation Guide
docs/english/GoGrab-Space/translations/translation-guide.md
# Translation System Guide
## Overview
GoGrab Space uses Transloco for translations with AI-powered management via Smartcat.
## For Developers
### Adding New Translations
1. Add translation key to English file:
\`\`\`json
// libs/libs-foundational/i18n/src/assets/i18n/en.json
{
"myFeature": {
"title": "My Feature",
"description": "Feature description"
}
}
\`\`\`
2. Use in HTML:
\`\`\`html
<h1 transloco="myFeature.title">My Feature</h1>
\`\`\`
3. Use in TypeScript:
\`\`\`typescript
title = this.translationService.translate('myFeature.title');
\`\`\`
4. Run AI translation:
\`\`\`bash
npm run translate:auto
\`\`\`
### Best Practices
- β
Use descriptive key names: `dashboard.welcome` not `d.w`
- β
Group related translations: `auth.login`, `auth.logout`
- β
Provide meaningful default text in templates
- β
Test all languages before merging
- β Don't hardcode text in components
- β Don't duplicate keys across namespaces
## For Content Creators
### Creating Multi-Language Content
When creating universes or pages:
1. Create content in your preferred language
2. System automatically translates to other languages
3. Review translations in Universe Settings
4. Edit if needed
## Translation Workflow
1. Developer adds keys β English JSON
2. Run `npm run translate:auto`
3. Smartcat AI translates
4. Download updated translations
5. Commit to repository
6. Deploy
## Supported Languages
- πΊπΈ English (primary)
- πͺπΈ Spanish
- π΅πΉ Portuguese
Want more languages? Contact admin.
Step 7.2: Update Main Documentation
Add link to translation guide in main navigation.
π Phase 8: Deployment (Week 3)
Step 8.1: Environment Variables
Add to .env files:
# Smartcat Configuration
SMARTCAT_API_KEY=your_api_key_here
SMARTCAT_ACCOUNT_ID=your_account_id_here
SMARTCAT_PROJECT_ID=your_project_id_here
Step 8.2: Update Backend Configuration
apis/backend-api/src/config/configuration.ts
export default () => ({
// ... existing config
smartcat: {
apiKey: process.env.SMARTCAT_API_KEY,
accountId: process.env.SMARTCAT_ACCOUNT_ID,
projectId: process.env.SMARTCAT_PROJECT_ID
}
});
Step 8.3: Deploy Checklist
- Translation files built and copied to dist
- Environment variables set in production
- Language switcher visible in UI
- Default language set to English
- Browser language auto-detection working
- All static translations loaded
- Dynamic translation API functional
- Smartcat integration active
π Monitoring & Maintenance
Translation Coverage Script
tools/scripts/translation/coverage.js
#!/usr/bin/env node
const fs = require('fs');
const glob = require('glob');
/**
* Check translation coverage in codebase
*/
class TranslationCoverage {
constructor() {
this.sourceFiles = glob.sync('apps/gograb-space/src/**/*.{ts,html}');
this.translationKeys = this.loadTranslationKeys();
}
loadTranslationKeys() {
const enFile = 'libs/libs-foundational/i18n/src/assets/i18n/en.json';
const data = JSON.parse(fs.readFileSync(enFile, 'utf-8'));
return this.flattenKeys(data);
}
flattenKeys(obj, prefix = '') {
let keys = [];
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object') {
keys = keys.concat(this.flattenKeys(value, fullKey));
} else {
keys.push(fullKey);
}
}
return keys;
}
analyze() {
let hardcodedTexts = [];
this.sourceFiles.forEach(file => {
const content = fs.readFileSync(file, 'utf-8');
// Find potential hardcoded text (simple heuristic)
const matches = content.match(/>([A-Z][a-z\s]{3,})</g);
if (matches) {
matches.forEach(match => {
const text = match.slice(1, -1).trim();
if (!this.translationKeys.some(k => k.includes(text.toLowerCase()))) {
hardcodedTexts.push({ file, text });
}
});
}
});
console.log('π Translation Coverage Report\n');
console.log(`Total translation keys: ${this.translationKeys.length}`);
console.log(`Source files scanned: ${this.sourceFiles.length}`);
console.log(`Potential hardcoded text: ${hardcodedTexts.length}\n`);
if (hardcodedTexts.length > 0) {
console.log('β οΈ Found potential hardcoded text:\n');
hardcodedTexts.slice(0, 10).forEach(({ file, text }) => {
console.log(` ${file}`);
console.log(` β "${text}"\n`);
});
} else {
console.log('β
No hardcoded text detected!');
}
}
}
const coverage = new TranslationCoverage();
coverage.analyze();
Monthly Maintenance Tasks
Review Translation Quality
- Check Smartcat dashboard
- Review user feedback
- Update translations if needed
Update Keys
- Run extraction:
npm run translate:extract - Review new keys
- Translate with AI:
npm run translate:auto
- Run extraction:
Monitor Usage
- Check language distribution
- Identify most-used languages
- Consider adding new languages
π Training Materials
For Developers
Quick Start Video Script (to be recorded):
Introduction (1 min)
- What is Transloco?
- Why we use it
- Overview of workflow
Adding Translations (3 min)
- Add key to English JSON
- Use in HTML template
- Use in TypeScript
- Run AI translation
Testing (2 min)
- Switch languages
- Verify translations
- Fix issues
For Content Creators
User Guide (to be created):
- Creating multi-language content
- Reviewing AI translations
- Editing translations
- Publishing in multiple languages
π§ Troubleshooting
Common Issues
Issue: Translations not loading
Symptoms: UI shows translation keys instead of text
Solution:
# Check if translation files exist
ls libs/libs-foundational/i18n/src/assets/i18n/
# Rebuild library
nx build i18n --skip-nx-cache
# Clear cache
nx reset
# Restart dev server
nx serve gograb-space
Issue: Language switch not working
Symptoms: Selected language doesn't change UI
Solution:
- Check browser console for errors
- Verify TranslocoModule imported
- Check if translation files loaded
- Clear localStorage:
localStorage.clear()
Issue: AI translation failed
Symptoms: Smartcat returns errors
Solution:
# Check API credentials
echo $SMARTCAT_API_KEY
# Verify project ID
echo $SMARTCAT_PROJECT_ID
# Check Smartcat dashboard
smartcat project list
# Manual upload if needed
smartcat file upload --project-id XXX --file libs/libs-foundational/i18n/src/assets/i18n/en.json
π Success Metrics
KPIs to Track
- Translation Coverage: % of UI translated
- AI Accuracy: Manual edits needed vs AI translations
- Language Usage: Distribution of user languages
- Load Time: Impact on initial bundle size
- Developer Time: Time saved vs manual translation
Target Goals
- π― 95%+ translation coverage
- π― < 5KB bundle size impact
- π― < 100ms language switch time
- π― 90%+ AI translation accuracy
- π― 80% reduction in translation time
π Launch Checklist
Pre-Launch
- All Phase 1-7 steps completed
- Translation files validated
- Language switcher tested in all apps
- Dynamic translation API tested
- Documentation complete
- Team training conducted
Launch Day
- Deploy to staging
- Test all languages
- Monitor error logs
- Verify Smartcat integration
- Deploy to production
- Announce feature to users
Post-Launch (Week 1)
- Monitor usage analytics
- Gather user feedback
- Fix any reported issues
- Run translation coverage check
- Update documentation if needed
π Support & Resources
Documentation Links
- Transloco Docs: https://ngneat.github.io/transloco/
- Smartcat API: https://developers.smartcat.com/
- Internal Guide:
/docs/english/GoGrab-Space/translations/
Team Contacts
- Translation Lead: [Name] - translation issues
- DevOps: [Name] - deployment issues
- Backend: [Name] - API issues
Smartcat Support
- Dashboard: https://smartcat.com/dashboard
- Support: [email protected]
- Documentation: https://help.smartcat.com
π¦ Next Steps
After completing this implementation:
Expand Languages
- Add French, German, etc.
- Based on user demand
SEO Optimization
- Implement multi-language URLs
- Add hreflang tags
- Create language-specific sitemaps
Advanced Features
- Context-aware translations
- Pluralization rules
- Date/number formatting
Performance Optimization
- Lazy load translations per module
- Implement translation caching
- Optimize bundle splitting
π Appendix
A. Complete File Structure
editor/
βββ libs/libs-foundational/i18n/
β βββ src/
β β βββ lib/
β β β βββ services/
β β β β βββ translation.service.ts
β β β β βββ transloco-loader.service.ts
β β β β βββ language-detector.service.ts
β β β β βββ dynamic-translation.service.ts
β β β βββ models/
β β β β βββ translation.models.ts
β β β β βββ language.types.ts
β β β βββ config/
β β β β βββ transloco.config.ts
β β β βββ utils/
β β β βββ translation.utils.ts
β β βββ assets/
β β β βββ i18n/
β β β βββ en.json
β β β βββ es.json
β β β βββ pt.json
β β βββ index.ts
β βββ project.json
β βββ tsconfig.lib.json
β βββ README.md
βββ apps/gograb-space/
β βββ src/
β βββ app/
β βββ components/
β β βββ shared/
β β βββ language-switcher/
β β βββ language-switcher.component.ts
β β βββ language-switcher.component.scss
β βββ app.config.ts (updated)
βββ apis/backend-api/
β βββ src/
β βββ modules/
β βββ translations/
β βββ translations.controller.ts
β βββ translations.service.ts
β βββ translations.module.ts
βββ tools/scripts/translation/
β βββ translate.js
β βββ test-translations.js
β βββ coverage.js
βββ transloco.config.js
βββ docs/english/GoGrab-Space/translations/
βββ translation-guide.md
B. NPM Scripts Summary
{
"translate:extract": "Extract keys from code",
"translate:upload": "Upload to Smartcat",
"translate:download": "Download from Smartcat",
"translate:auto": "Full automated workflow",
"translate:test": "Validate translation files",
"translate:coverage": "Check translation coverage"
}
C. Environment Variables
# Required
SMARTCAT_API_KEY=
SMARTCAT_ACCOUNT_ID=
SMARTCAT_PROJECT_ID=
# Optional
DEFAULT_LANGUAGE=en
SUPPORTED_LANGUAGES=en,es,pt
TRANSLATION_CACHE_TTL=3600
D. Dependencies
{
"dependencies": {
"@ngneat/transloco": "^6.0.0"
},
"devDependencies": {
"@ngneat/transloco-keys-manager": "^3.0.0",
"smartcat-cli": "^1.0.0"
}
}
β Final Notes
This implementation plan provides:
- Complete Step-by-Step Guide: Every detail from setup to deployment
- Production-Ready Code: All necessary services, components, and configurations
- AI Automation: Full Smartcat integration for automated translations
- Best Practices: Following Angular 20 and Transloco recommendations
- Testing & Validation: Scripts and checklists for quality assurance
- Documentation: Comprehensive guides for developers and users
- Troubleshooting: Common issues and solutions
- Monitoring: Tools to track coverage and quality
Estimated Timeline: 2-3 weeks with 1-2 developers
Estimated Cost: Smartcat Free tier initially, ~$100/month for pro features
Expected ROI: 80% time savings on translation, support for global users
Document Version: 1.0
Last Updated: November 20, 2025
Status: Ready for Implementation
Next Review: After Phase 1 completion
Questions or Issues?
Contact the GoGrab Space team or refer to the resources section above.