DeployStack Docs

Internationalization (i18n)

DeployStack supports multiple languages through Vue I18n with a modular file structure that organizes translations by feature. This approach makes it easy to maintain translations and add new languages.

Architecture Overview

The i18n system is designed with modularity and maintainability in mind:

  • Feature-based organization: Translations are grouped by application features
  • Modular structure: Each feature has its own translation file
  • Scalable approach: Easy to add new languages and features
  • Type safety: TypeScript support for translation keys

Directory Structure

src/i18n/
├── index.ts               # Main i18n initialization
└── locales/
    ├── en/                # English translations
   ├── index.ts       # Exports all English translations
   ├── common.ts      # Common translations (buttons, labels, etc.)
   ├── login.ts       # Login page specific translations
   ├── register.ts    # Register page specific translations
   ├── dashboard.ts   # Dashboard specific translations
   ├── mcp-servers.ts # MCP server management translations
   └── deployment.ts  # Deployment related translations
    └── de/                # German translations (example)
        ├── index.ts
        ├── common.ts
        └── ...

Setting Up i18n

Main Configuration

The main i18n configuration in src/i18n/index.ts:

import { createI18n } from 'vue-i18n'
import en from './locales/en'
import de from './locales/de' // Example additional language

const i18n = createI18n({
  legacy: false,              // Use Composition API
  locale: 'en',              // Default language
  fallbackLocale: 'en',      // Fallback language
  messages: {
    en,
    de
  }
})

export default i18n

Locale Index File

Each locale has an index file that exports all translations:

// src/i18n/locales/en/index.ts
import common from './common'
import login from './login'
import register from './register'
import dashboard from './dashboard'
import mcpServers from './mcp-servers'
import deployment from './deployment'

export default {
  common,
  login,
  register,
  dashboard,
  mcpServers,
  deployment
}

Creating Translation Files

Common Translations

File: src/i18n/locales/en/common.ts

export default {
  // Navigation
  navigation: {
    dashboard: 'Dashboard',
    mcpServers: 'MCP Servers',
    deployments: 'Deployments',
    settings: 'Settings',
    logout: 'Logout'
  },
  
  // Common buttons and actions
  buttons: {
    save: 'Save',
    cancel: 'Cancel',
    delete: 'Delete',
    edit: 'Edit',
    create: 'Create',
    deploy: 'Deploy',
    stop: 'Stop',
    restart: 'Restart',
    view: 'View',
    download: 'Download'
  },
  
  // Status indicators
  status: {
    running: 'Running',
    stopped: 'Stopped',
    error: 'Error',
    pending: 'Pending',
    deploying: 'Deploying',
    healthy: 'Healthy',
    unhealthy: 'Unhealthy'
  },
  
  // Common labels
  labels: {
    name: 'Name',
    description: 'Description',
    status: 'Status',
    created: 'Created',
    updated: 'Updated',
    version: 'Version',
    author: 'Author',
    email: 'Email',
    password: 'Password'
  },
  
  // Validation messages
  validation: {
    required: 'This field is required',
    email: 'Please enter a valid email address',
    minLength: 'Must be at least {min} characters',
    maxLength: 'Must be no more than {max} characters',
    passwordMismatch: 'Passwords do not match'
  },
  
  // Common messages
  messages: {
    loading: 'Loading...',
    saving: 'Saving...',
    saved: 'Saved successfully',
    error: 'An error occurred',
    noData: 'No data available',
    confirmDelete: 'Are you sure you want to delete this item?'
  }
}

Feature-Specific Translations

File: src/i18n/locales/en/mcp-servers.ts

export default {
  title: 'MCP Servers',
  subtitle: 'Manage your Model Context Protocol servers',
  
  catalog: {
    title: 'MCP Server Catalog',
    description: 'Browse and deploy MCP servers from our community catalog',
    search: 'Search servers...',
    categories: {
      all: 'All Categories',
      databases: 'Databases',
      apis: 'APIs',
      tools: 'Development Tools',
      productivity: 'Productivity',
      integrations: 'Integrations'
    }
  },
  
  deployment: {
    title: 'Deploy MCP Server',
    selectProvider: 'Select Cloud Provider',
    configure: 'Configure Deployment',
    credentials: {
      title: 'Credentials',
      description: 'Configure API keys and authentication',
      addCredential: 'Add Credential',
      apiKey: 'API Key',
      secretKey: 'Secret Key',
      token: 'Access Token'
    },
    environment: {
      title: 'Environment Variables',
      description: 'Configure environment variables for your MCP server',
      addVariable: 'Add Variable',
      key: 'Variable Name',
      value: 'Variable Value'
    }
  },
  
  management: {
    myServers: 'My Deployed Servers',
    serverDetails: 'Server Details',
    logs: 'View Logs',
    metrics: 'Performance Metrics',
    scale: 'Scale Server',
    instances: 'Instances',
    uptime: 'Uptime',
    lastDeployed: 'Last Deployed'
  },
  
  forms: {
    serverName: {
      label: 'Server Name',
      placeholder: 'Enter a name for your server deployment',
      description: 'This will be used to identify your deployment'
    },
    region: {
      label: 'Deployment Region',
      placeholder: 'Select region',
      description: 'Choose the region closest to your users'
    }
  },
  
  actions: {
    deployNow: 'Deploy Now',
    viewInCatalog: 'View in Catalog',
    manageServer: 'Manage Server',
    viewLogs: 'View Logs',
    stopServer: 'Stop Server',
    restartServer: 'Restart Server',
    deleteDeployment: 'Delete Deployment'
  },
  
  notifications: {
    deploymentStarted: 'Deployment started successfully',
    deploymentCompleted: 'Server deployed successfully',
    deploymentFailed: 'Deployment failed: {error}',
    serverStopped: 'Server stopped successfully',
    serverRestarted: 'Server restarted successfully',
    serverDeleted: 'Server deployment deleted'
  }
}

Using Translations in Components

Basic Usage

<script setup lang="ts">
import { useI18n } from 'vue-i18n'

const { t } = useI18n()
</script>

<template>
  <div class="mcp-server-card">
    <!-- Simple translation -->
    <h2>{{ $t('mcpServers.title') }}</h2>
    
    <!-- Translation in script -->
    <p>{{ t('mcpServers.subtitle') }}</p>
    
    <!-- Translation with parameters -->
    <p>{{ $t('common.validation.minLength', { min: 8 }) }}</p>
    
    <!-- Translation in attributes -->
    <input :placeholder="$t('mcpServers.catalog.search')" />
    
    <!-- Translation in v-if -->
    <div v-if="isLoading">{{ $t('common.messages.loading') }}</div>
  </div>
</template>

Advanced Usage with Computed Properties

<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

const { t } = useI18n()

interface Props {
  serverStatus: 'running' | 'stopped' | 'error'
  errorMessage?: string
}

const props = defineProps<Props>()

const statusText = computed(() => {
  return t(`common.status.${props.serverStatus}`)
})

const errorMessage = computed(() => {
  if (props.serverStatus === 'error' && props.errorMessage) {
    return t('mcpServers.notifications.deploymentFailed', { 
      error: props.errorMessage 
    })
  }
  return null
})
</script>

<template>
  <div class="server-status">
    <span class="status-badge" :class="serverStatus">
      {{ statusText }}
    </span>
    <p v-if="errorMessage" class="error-message">
      {{ errorMessage }}
    </p>
  </div>
</template>

Form Validation with i18n

<script setup lang="ts">
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'

const { t } = useI18n()

const email = ref('')
const password = ref('')

const emailError = computed(() => {
  if (!email.value) {
    return t('common.validation.required')
  }
  if (!isValidEmail(email.value)) {
    return t('common.validation.email')
  }
  return null
})

const passwordError = computed(() => {
  if (!password.value) {
    return t('common.validation.required')
  }
  if (password.value.length < 8) {
    return t('common.validation.minLength', { min: 8 })
  }
  return null
})

function isValidEmail(email: string): boolean {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  return emailRegex.test(email)
}
</script>

<template>
  <form class="login-form">
    <div class="form-field">
      <label>{{ $t('common.labels.email') }}</label>
      <input 
        v-model="email" 
        type="email" 
        :placeholder="$t('login.form.email.placeholder')"
        :class="{ error: emailError }"
      />
      <span v-if="emailError" class="error-text">{{ emailError }}</span>
    </div>
    
    <div class="form-field">
      <label>{{ $t('common.labels.password') }}</label>
      <input 
        v-model="password" 
        type="password" 
        :placeholder="$t('login.form.password.placeholder')"
        :class="{ error: passwordError }"
      />
      <span v-if="passwordError" class="error-text">{{ passwordError }}</span>
    </div>
    
    <button type="submit" :disabled="emailError || passwordError">
      {{ $t('login.form.submit') }}
    </button>
  </form>
</template>

Adding New Languages

1. Create Language Directory

mkdir -p src/i18n/locales/de

2. Create Translation Files

Start with the common translations:

// src/i18n/locales/de/common.ts
export default {
  navigation: {
    dashboard: 'Dashboard',
    mcpServers: 'MCP Server',
    deployments: 'Bereitstellungen',
    settings: 'Einstellungen',
    logout: 'Abmelden'
  },
  
  buttons: {
    save: 'Speichern',
    cancel: 'Abbrechen',
    delete: 'Löschen',
    edit: 'Bearbeiten',
    create: 'Erstellen',
    deploy: 'Bereitstellen',
    stop: 'Stoppen',
    restart: 'Neu starten',
    view: 'Anzeigen',
    download: 'Herunterladen'
  },
  
  // ... continue with other translations
}

3. Create Locale Index

// src/i18n/locales/de/index.ts
import common from './common'
import login from './login'
import register from './register'
// ... import other feature translations

export default {
  common,
  login,
  register,
  // ... export other translations
}

4. Update Main i18n Configuration

// src/i18n/index.ts
import en from './locales/en'
import de from './locales/de'

const i18n = createI18n({
  legacy: false,
  locale: 'en',
  fallbackLocale: 'en',
  messages: {
    en,
    de  // Add the new language
  }
})

Language Switching

Language Selector Component

<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

const { locale, availableLocales } = useI18n()

const languages = computed(() => [
  { code: 'en', name: 'English', flag: '🇺🇸' },
  { code: 'de', name: 'Deutsch', flag: '🇩🇪' },
  // Add more languages as needed
])

function changeLanguage(langCode: string) {
  locale.value = langCode
  // Optionally save to localStorage
  localStorage.setItem('preferred-language', langCode)
}

// Load saved language preference
const savedLanguage = localStorage.getItem('preferred-language')
if (savedLanguage && availableLocales.includes(savedLanguage)) {
  locale.value = savedLanguage
}
</script>

<template>
  <div class="language-selector">
    <select 
      :value="locale" 
      @change="changeLanguage($event.target.value)"
      class="language-select"
    >
      <option 
        v-for="lang in languages" 
        :key="lang.code" 
        :value="lang.code"
      >
        {{ lang.flag }} {{ lang.name }}
      </option>
    </select>
  </div>
</template>

Best Practices

1. Translation Key Naming

Use descriptive, hierarchical naming:

// Good
'mcpServers.deployment.credentials.title'
'common.validation.passwordTooShort'
'dashboard.widgets.serverStatus.healthy'

// Avoid
'title'
'error1'
'msg'

2. Parameterized Messages

Use parameters for dynamic content:

// Translation file
export default {
  messages: {
    welcomeUser: 'Welcome back, {userName}!',
    itemsCount: 'You have {count} {count, plural, one {item} other {items}}',
    deploymentTime: 'Deployed {timeAgo} ago'
  }
}

// Usage
t('messages.welcomeUser', { userName: 'John' })
t('messages.itemsCount', { count: 5 })

3. Context-Aware Translations

Group related translations together:

export default {
  serverStatus: {
    running: {
      label: 'Running',
      description: 'Server is operating normally',
      action: 'Stop Server'
    },
    stopped: {
      label: 'Stopped',
      description: 'Server is not running',
      action: 'Start Server'
    }
  }
}

4. Handle Missing Translations

Always provide fallback values:

<template>
  <!-- The fallback will be used if translation is missing -->
  <h1>{{ $t('page.title', 'Default Page Title') }}</h1>
</template>

TypeScript Integration

Translation Key Types

Create type definitions for better IDE support:

// types/i18n.ts
export interface I18nMessages {
  common: {
    buttons: {
      save: string
      cancel: string
      // ... other button translations
    }
    // ... other common translations
  }
  mcpServers: {
    title: string
    deployment: {
      title: string
      // ... other deployment translations
    }
    // ... other MCP server translations
  }
}

// Extend the vue-i18n module
declare module 'vue-i18n' {
  export interface DefineLocaleMessage extends I18nMessages {}
}

Typed Translation Function

import { useI18n } from 'vue-i18n'
import type { I18nMessages } from '@/types/i18n'

// Create a typed version of the translation function
export function useTypedI18n() {
  const { t, ...rest } = useI18n<I18nMessages>()
  return { t, ...rest }
}

Testing Translations

Testing Translation Keys

// tests/i18n.test.ts
import { describe, it, expect } from 'vitest'
import en from '@/i18n/locales/en'

describe('i18n translations', () => {
  it('should have all required common translations', () => {
    expect(en.common.buttons.save).toBeDefined()
    expect(en.common.buttons.cancel).toBeDefined()
    expect(en.common.validation.required).toBeDefined()
  })
  
  it('should have MCP server translations', () => {
    expect(en.mcpServers.title).toBeDefined()
    expect(en.mcpServers.deployment.title).toBeDefined()
  })
})

Component Translation Testing

// tests/components/LanguageSelector.test.ts
import { mount } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'
import LanguageSelector from '@/components/LanguageSelector.vue'

const i18n = createI18n({
  legacy: false,
  locale: 'en',
  messages: {
    en: { test: 'Test' },
    de: { test: 'Test' }
  }
})

describe('LanguageSelector', () => {
  it('should change language when selected', async () => {
    const wrapper = mount(LanguageSelector, {
      global: {
        plugins: [i18n]
      }
    })
    
    const select = wrapper.find('select')
    await select.setValue('de')
    
    expect(i18n.global.locale.value).toBe('de')
  })
})

Performance Considerations

Lazy Loading Translations

For large applications, consider lazy loading translation files:

// src/i18n/index.ts
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  legacy: false,
  locale: 'en',
  fallbackLocale: 'en',
  messages: {}
})

// Lazy load function
async function loadLocaleMessages(locale: string) {
  if (i18n.global.availableLocales.includes(locale)) {
    return
  }
  
  try {
    const messages = await import(`./locales/${locale}/index.ts`)
    i18n.global.setLocaleMessage(locale, messages.default)
  } catch (error) {
    console.error(`Failed to load locale ${locale}:`, error)
  }
}

// Usage in components
export async function setLanguage(locale: string) {
  await loadLocaleMessages(locale)
  i18n.global.locale.value = locale
}

Translation Caching

Implement caching for frequently used translations:

// utils/translationCache.ts
const cache = new Map<string, string>()

export function getCachedTranslation(key: string, params?: any): string | null {
  const cacheKey = `${key}-${JSON.stringify(params || {})}`
  return cache.get(cacheKey) || null
}

export function setCachedTranslation(key: string, params: any, value: string): void {
  const cacheKey = `${key}-${JSON.stringify(params || {})}`
  cache.set(cacheKey, value)
}

Common Patterns

Form Validation Messages

// i18n/locales/en/validation.ts
export default {
  serverName: {
    required: 'Server name is required',
    minLength: 'Server name must be at least 3 characters',
    maxLength: 'Server name cannot exceed 50 characters',
    invalidChars: 'Server name can only contain letters, numbers, and hyphens'
  },
  deployment: {
    region: {
      required: 'Please select a deployment region',
      invalid: 'Selected region is not available'
    },
    credentials: {
      apiKey: {
        required: 'API key is required',
        invalid: 'Invalid API key format'
      }
    }
  }
}

Status and Notification Messages

// i18n/locales/en/notifications.ts
export default {
  success: {
    serverDeployed: 'MCP server deployed successfully',
    configurationSaved: 'Configuration saved',
    credentialsUpdated: 'Credentials updated securely'
  },
  error: {
    deploymentFailed: 'Deployment failed: {reason}',
    networkError: 'Network connection error. Please try again.',
    unauthorized: 'You are not authorized to perform this action',
    serverNotFound: 'Server not found or has been deleted'
  },
  warning: {
    unsavedChanges: 'You have unsaved changes. Are you sure you want to leave?',
    serverRestarting: 'Server is restarting. This may take a few minutes.',
    quotaExceeded: 'You have reached your deployment quota'
  }
}
// i18n/locales/en/navigation.ts
export default {
  main: {
    dashboard: 'Dashboard',
    catalog: 'MCP Catalog',
    deployments: 'My Deployments',
    teams: 'Teams',
    settings: 'Settings'
  },
  breadcrumbs: {
    home: 'Home',
    mcpServers: 'MCP Servers',
    deployment: 'Deployment',
    configuration: 'Configuration'
  },
  actions: {
    deploy: 'Deploy Server',
    configure: 'Configure',
    manage: 'Manage',
    monitor: 'Monitor',
    scale: 'Scale'
  }
}

Internationalization Checklist

Before Adding New Features

  • Plan translation structure for new components
  • Identify reusable common translations
  • Consider context and parameterization needs
  • Plan for pluralization if needed

During Development

  • Use translation keys instead of hardcoded text
  • Add proper TypeScript types for new translations
  • Test with different languages if available
  • Consider text length variations between languages

Before Release

  • Ensure all user-facing text is translatable
  • Test language switching functionality
  • Verify fallback translations work
  • Check for missing translation keys
  • Test form validation messages in different languages

Maintenance

Regular Translation Updates

  1. Review translation completeness for each supported language
  2. Update outdated translations when features change
  3. Add missing translations for new features
  4. Remove unused translation keys to keep files clean

Translation File Organization

Keep translation files organized and maintainable:

# Good organization
src/i18n/locales/en/
├── common.ts          # Shared across app
├── navigation.ts      # Navigation items
├── validation.ts      # Form validation
├── notifications.ts   # Success/error messages
├── features/
   ├── mcp-servers.ts
   ├── deployment.ts
   └── dashboard.ts

Version Control Best Practices

  • Keep translation files in version control
  • Use meaningful commit messages for translation changes
  • Consider separate PRs for translation updates
  • Document translation conventions in your project

This comprehensive i18n setup ensures your DeployStack frontend can grow to support multiple languages while maintaining clean, organized, and maintainable translation files.