feat: Add multilingual deployment progress messages
Some checks failed
Build and Push Docker Image (Production) / build-and-push-main (push) Failing after 3m9s
Build and Push Docker Image (Staging) / build-and-push-staging (push) Failing after 30s

- Created backend i18n system with EN/NL/AR translations
- Frontend now sends language preference with deployment request
- Backend deployment messages follow user's selected language
- Translated key messages: initializing, creating app, SSL waiting, etc.
- Added top margin (100px) on mobile to prevent language button overlap

Fixes real-time deployment status showing English regardless of language selection.
This commit is contained in:
Oussama Douhou
2026-01-13 16:40:05 +01:00
parent dd41bb5a6a
commit 86fe7a8bf1
4 changed files with 89 additions and 15 deletions

View File

@@ -11,6 +11,7 @@
*/
import { DokployProductionClient } from '../api/dokploy-production.js';
import { createTranslator, type BackendLanguage } from '../lib/i18n-backend.js';
export interface DeploymentConfig {
stackName: string;
@@ -22,6 +23,7 @@ export interface DeploymentConfig {
registryId?: string;
sharedProjectId?: string;
sharedEnvironmentId?: string;
lang?: string;
}
export interface DeploymentState {
@@ -71,10 +73,12 @@ export type ProgressCallback = (state: DeploymentState) => void;
export class ProductionDeployer {
private client: DokployProductionClient;
private progressCallback?: ProgressCallback;
private t: ReturnType<typeof createTranslator>;
constructor(client: DokployProductionClient, progressCallback?: ProgressCallback) {
this.client = client;
this.progressCallback = progressCallback;
this.t = createTranslator('en');
}
private notifyProgress(state: DeploymentState): void {
@@ -87,13 +91,15 @@ export class ProductionDeployer {
* Deploy a complete AI stack with full production safeguards
*/
async deploy(config: DeploymentConfig): Promise<DeploymentResult> {
this.t = createTranslator((config.lang || 'en') as BackendLanguage);
const state: DeploymentState = {
id: `dep_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
stackName: config.stackName,
phase: 'initializing',
status: 'in_progress',
progress: 0,
message: 'Initializing deployment',
message: this.t('initializing'),
resources: {},
timestamps: {
started: new Date().toISOString(),
@@ -228,12 +234,12 @@ export class ProductionDeployer {
private async getEnvironment(state: DeploymentState): Promise<void> {
state.phase = 'getting_environment';
state.progress = 25;
state.message = 'Getting environment ID';
state.message = this.t('gettingEnvironment');
// Skip if we already have environment ID from project creation
if (state.resources.environmentId) {
console.log('Environment ID already available from project creation');
state.message = 'Environment ID already available';
state.message = this.t('environmentAvailable');
return;
}
@@ -243,7 +249,7 @@ export class ProductionDeployer {
const environment = await this.client.getDefaultEnvironment(state.resources.projectId);
state.resources.environmentId = environment.environmentId;
state.message = 'Environment ID retrieved';
state.message = this.t('environmentRetrieved');
}
private async createOrFindApplication(
@@ -252,7 +258,7 @@ export class ProductionDeployer {
): Promise<void> {
state.phase = 'creating_application';
state.progress = 40;
state.message = 'Creating application';
state.message = this.t('creatingApplication');
if (!state.resources.environmentId) {
throw new Error('Environment ID not available');
@@ -279,7 +285,7 @@ export class ProductionDeployer {
): Promise<void> {
state.phase = 'configuring_application';
state.progress = 50;
state.message = 'Configuring application with Docker image';
state.message = this.t('configuringApplication');
if (!state.resources.applicationId) {
throw new Error('Application ID not available');
@@ -332,7 +338,7 @@ export class ProductionDeployer {
): Promise<void> {
state.phase = 'creating_domain';
state.progress = 70;
state.message = 'Creating domain';
state.message = this.t('creatingDomain');
if (!state.resources.applicationId) {
throw new Error('Application ID not available');
@@ -359,7 +365,7 @@ export class ProductionDeployer {
private async deployApplication(state: DeploymentState): Promise<void> {
state.phase = 'deploying';
state.progress = 85;
state.message = 'Triggering deployment';
state.message = this.t('deployingApplication');
if (!state.resources.applicationId) {
throw new Error('Application ID not available');
@@ -375,7 +381,7 @@ export class ProductionDeployer {
): Promise<void> {
state.phase = 'verifying_health';
state.progress = 95;
state.message = 'Verifying application status via Dokploy';
state.message = this.t('verifyingHealth');
if (!state.resources.applicationId) {
throw new Error('Application ID not available');
@@ -392,13 +398,13 @@ export class ProductionDeployer {
console.log(`Application status: ${appStatus}`);
if (appStatus === 'done') {
state.message = 'Waiting for SSL certificate provisioning...';
state.message = this.t('waitingForSSL');
state.progress = 98;
this.notifyProgress(state);
await this.sleep(15000);
state.message = 'Application deployed successfully';
state.message = this.t('deploymentSuccess');
return;
}
@@ -410,7 +416,7 @@ export class ProductionDeployer {
}
const elapsed = Math.round((Date.now() - startTime) / 1000);
state.message = `Waiting for application to start (${elapsed}s)...`;
state.message = `${this.t('waitingForStart')} (${elapsed}s)...`;
this.notifyProgress(state);
await this.sleep(interval);