LunariaJS Ultimate Guide - Building an Enterprise-grade Multilingual Documentation Platform
Apply all the knowledge from this series to build a complete enterprise-grade multilingual documentation platform from scratch, covering project planning, architecture design, complete configuration, CI/CD integration, team collaboration, and all other aspects.
LunariaJS Ultimate Guide - Building an Enterprise-grade Multilingual Documentation Platform
Congratulations on completing the nine articles in the LunariaJS series! Today, let’s combine all the knowledge we’ve learned to build a complete enterprise-grade multilingual documentation platform from scratch. This is the final chapter of this series and the best way to test your learning outcomes.
Official Documentation: LunariaJS Documentation
Project Planning
Requirements Analysis
Project Background: We need to build a multilingual documentation site for an open source project with the following requirements:
| Requirement | Description | Priority |
|---|---|---|
| Multilingual support | Support Chinese, Japanese, Korean (4 languages total) | High |
| Translation status visibility | Real-time tracking of translation progress | High |
| Team collaboration | Support community contributor participation in translation | High |
| Automation | CI/CD auto build and deployment | Medium |
| Performance optimization | Fast loading and response | Medium |
| SEO optimization | Search engine friendly | Medium |
Tech Stack Selection
Based on requirements analysis, we choose the following tech stack:
| Technology | Purpose | Reason for Choice |
|---|---|---|
| Astro 5.0 | Framework | High performance, SEO friendly |
| Starlight | Documentation theme | Out-of-the-box documentation solution |
| LunariaJS | Translation management | Git-based workflow, visual dashboard |
| GitHub Actions | CI/CD | Deep integration with GitHub |
| Vercel | Hosting | Fast deployment, edge network |
Architecture Design
+-------------------------------------------------------------+
| User Layer |
| (Developers/Contributors/Readers) |
+-------------------------------------------------------------+
| Access Layer |
| +------------+ +------------+ +------------+ |
| | Doc Site | | Trans | | GitHub | |
| | (Vercel) | | Dashboard | | Repo | |
| +------------+ +------------+ +------------+ |
+-------------------------------------------------------------+
| Application Layer |
| +-------------------------------------------------------+ |
| | Astro + Starlight | |
| | +-----------+ +-----------+ +-----------+ | |
| | | Content | | i18n | | Search | | |
| | | Management| | Routing | | Features | | |
| | +-----------+ +-----------+ +-----------+ | |
| +-------------------------------------------------------+ |
| +-------------------------------------------------------+ |
| | LunariaJS | |
| | +-----------+ +-----------+ +-----------+ | |
| | | Status | | Dashboard | | Git | | |
| | | Tracking | | Generation| | Integration| | |
| | +-----------+ +-----------+ +-----------+ | |
| +-------------------------------------------------------+ |
+-------------------------------------------------------------+
| Infrastructure Layer |
| +------------+ +------------+ +------------+ |
| | Git | | GitHub | | CI/CD | |
| | (VCS) | | (Hosting) | | (Automation)| |
| +------------+ +------------+ +------------+ |
+-------------------------------------------------------------+
Tech Stack Setup
Step 1: Initialize Project
# Create project using Starlight template
npm create astro@latest my-docs -- --template starlight
# Enter project directory
cd my-docs
# Install LunariaJS
npm install @lunariajs/starlight
# Initialize Git (if not already)
git init
git add .
git commit -m "Initial commit"
Step 2: Configure Astro
// astro.config.mjs
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import lunaria from '@lunariajs/starlight';
// Language configuration
const languages = ['en', 'zh-cn', 'ja', 'ko'];
const sourceLanguage = 'en';
const languageLabels = {
en: 'English',
'zh-cn': 'Simplified Chinese',
ja: 'Japanese',
ko: 'Korean',
};
export default defineConfig({
site: 'https://docs.example.com',
integrations: [
starlight({
title: 'My Project Documentation',
defaultLocale: sourceLanguage,
locales: Object.fromEntries(
languages.map(lang => [
lang,
{ label: languageLabels[lang], lang }
])
),
sidebar: {
en: [
{ label: 'Home', link: '/' },
{
label: 'Getting Started',
items: [
{ label: 'Introduction', link: '/introduction/' },
{ label: 'Installation', link: '/installation/' },
{ label: 'Quick Start', link: '/quick-start/' },
],
},
{
label: 'Guides',
items: [
{ label: 'Configuration', link: '/guides/configuration/' },
{ label: 'Deployment', link: '/guides/deployment/' },
],
},
{
label: 'Reference',
items: [
{ label: 'API', link: '/reference/api/' },
{ label: 'CLI', link: '/reference/cli/' },
],
},
],
'zh-cn': [
{ label: 'Home', link: '/' },
{
label: 'Getting Started',
items: [
{ label: 'Introduction', link: '/introduction/' },
{ label: 'Installation', link: '/installation/' },
{ label: 'Quick Start', link: '/quick-start/' },
],
},
// ...
],
// Other languages...
},
social: {
github: 'https://github.com/your-org/your-repo',
},
head: [
{ tag: 'meta', attrs: { name: 'theme-color', content: '#6366f1' } },
],
}),
lunaria({
sourceLanguage,
languages,
files: [
{
sourcePath: 'src/content/docs/{lang}/{slug}.md',
localizationPath: 'src/content/docs/{lang}/{slug}.md',
},
],
dashboard: {
outputDir: 'public/i18n-status',
title: 'Translation Status',
description: 'Track the progress of documentation translations',
},
}),
],
});
Multilingual Content Organization
Directory Structure
src/content/docs/
+-- en/ # English (source language)
| +-- index.md
| +-- introduction.md
| +-- installation.md
| +-- quick-start.md
| +-- guides/
| | +-- configuration.md
| | +-- deployment.md
| +-- reference/
| +-- api.md
| +-- cli.md
+-- zh-cn/ # Chinese translation
| +-- index.md
| +-- introduction.md
| +-- installation.md
| +-- quick-start.md
| +-- guides/
| | +-- configuration.md # deployment.md not yet translated
| +-- reference/
| +-- api.md
+-- ja/ # Japanese translation
| +-- index.md
| +-- introduction.md
| +-- installation.md
+-- ko/ # Korean translation
+-- index.md
Content Template
---
# src/content/docs/en/introduction.md
title: Introduction
description: Get started with My Project and learn about its core concepts.
---
# Introduction
Welcome to **My Project**! This guide will help you understand the fundamentals.
## What is My Project?
My Project is a powerful tool that helps you...
## Key Features
- **Feature 1**: Description of feature 1
- **Feature 2**: Description of feature 2
- **Feature 3**: Description of feature 3
## Who Should Use This?
My Project is perfect for:
1. Developers building modern applications
2. Teams working on large-scale projects
3. Anyone who values performance and DX
## Next Steps
Ready to get started? Check out our [Installation Guide](/installation/).
Translation version example:
---
# src/content/docs/zh-cn/introduction.md
title: Introduction
description: Start using My Project and understand its core concepts.
---
# Introduction
Welcome to **My Project**! This guide will help you understand the fundamentals.
## What is My Project?
My Project is a powerful tool that helps you...
## Key Features
- **Feature 1**: Description of feature 1
- **Feature 2**: Description of feature 2
- **Feature 3**: Description of feature 3
## Who Should Use This?
My Project is perfect for:
1. Developers building modern applications
2. Teams working on large-scale projects
3. Anyone who values performance and DX
## Next Steps
Ready to get started? Check out our [Installation Guide](/installation/).
Complete Configuration Files
i18n Shared Configuration
// src/i18n-config.ts
export const languages = ['en', 'zh-cn', 'ja', 'ko'] as const;
export const sourceLanguage = 'en';
export type Language = (typeof languages)[number];
export const languageLabels: Record<Language, string> = {
en: 'English',
'zh-cn': 'Simplified Chinese',
ja: 'Japanese',
ko: 'Korean',
};
export const languageDirections: Record<Language, 'ltr' | 'rtl'> = {
en: 'ltr',
'zh-cn': 'ltr',
ja: 'ltr',
ko: 'ltr',
};
export const defaultLocale = sourceLanguage;
TypeScript Configuration
// tsconfig.json
{
"extends": "astro/tsconfigs/strictest",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@i18n/*": ["src/i18n/*"],
"@content/*": ["src/content/*"]
},
"jsx": "react-jsx",
"jsxImportSource": "react"
},
"include": ["src/**/*", "astro.config.mjs"],
"exclude": ["node_modules", "dist"]
}
Content Collection Configuration
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
import { docsLoader, docsSchema } from '@astrojs/starlight';
const docs = defineCollection({
loader: docsLoader(),
schema: docsSchema({
extend: z.object({
// Extended fields
author: z.string().optional(),
difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(),
estimatedTime: z.string().optional(),
}),
}),
});
export const collections = { docs };
Localization Dashboard Customization
Custom Styles
/* src/styles/lunaria-custom.css */
:root {
/* Use Starlight's color variables */
--lunaria-color-primary: var(--sl-color-accent);
--lunaria-color-success: #22c55e;
--lunaria-color-warning: #f59e0b;
--lunaria-color-danger: #ef4444;
/* Background colors */
--lunaria-bg-primary: var(--sl-color-bg);
--lunaria-bg-secondary: var(--sl-color-bg-sidebar);
/* Text colors */
--lunaria-text-primary: var(--sl-color-text);
--lunaria-text-secondary: var(--sl-color-text-muted);
/* Borders */
--lunaria-border-color: var(--sl-color-hairline-light);
--lunaria-border-radius: 0.5rem;
}
.dark {
--lunaria-bg-primary: var(--sl-color-bg);
--lunaria-bg-secondary: var(--sl-color-bg-sidebar);
--lunaria-text-primary: var(--sl-color-text);
--lunaria-text-secondary: var(--sl-color-text-muted);
}
/* Custom card styles */
.language-card {
border: 1px solid var(--lunaria-border-color);
border-radius: var(--lunaria-border-radius);
padding: 1.5rem;
transition: transform 0.2s, box-shadow 0.2s;
}
.language-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* Progress bar animation */
.progress-bar {
transition: width 0.5s ease-out;
}
/* Status badges */
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
}
.status-badge.done {
background-color: rgba(34, 197, 94, 0.1);
color: #22c55e;
}
.status-badge.outdated {
background-color: rgba(245, 158, 11, 0.1);
color: #f59e0b;
}
.status-badge.missing {
background-color: rgba(239, 68, 68, 0.1);
color: #ef4444;
}
Navigation Integration
---
// src/components/I18nStatusLink.astro
---
<a
href="/i18n-status/"
class="i18n-status-link"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
<span>Translation Status</span>
</a>
<style>
.i18n-status-link {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--sl-color-bg);
border: 1px solid var(--sl-color-hairline-light);
border-radius: 0.375rem;
color: var(--sl-color-text);
text-decoration: none;
font-size: 0.875rem;
transition: background-color 0.2s, border-color 0.2s;
}
.i18n-status-link:hover {
background: var(--sl-color-bg-hover);
border-color: var(--sl-color-accent);
}
</style>
Complete CI/CD Pipeline
GitHub Actions Workflow
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
env:
NODE_VERSION: '20'
jobs:
# Job 1: Code checks
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linters
run: npm run lint
- name: Check formatting
run: npm run format:check
# Job 2: Build check
build:
name: Build
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Get complete history for LunariaJS
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build Astro site
run: npm run build
- name: Build Lunaria dashboard
run: npx lunaria build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 7
# Job 3: Translation status check
i18n-check:
name: Translation Check
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Check translation status
id: i18n
run: |
npx lunaria build --silent
# Generate status report
REPORT=$(node scripts/i18n-report.js)
echo "report<<EOF" >> $GITHUB_OUTPUT
echo "$REPORT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const report = `${{ steps.i18n.outputs.report }}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Translation Status\n\n${report}`
});
# Job 4: Deploy (main branch only)
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [build, i18n-check]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
permissions:
contents: read
deployments: write
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-output
path: dist
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
working-directory: ./
- name: Send deployment notification
if: success()
run: |
curl -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \
-H 'Content-type: application/json' \
-d '{
"text": "Documentation deployed successfully!",
"attachments": [{
"fields": [
{ "title": "Branch", "value": "${{ github.ref_name }}", "short": true },
{ "title": "Commit", "value": "${{ github.sha }}", "short": true }
]
}]
}'
Translation Status Report Script
// scripts/i18n-report.js
const fs = require('fs');
const path = require('path');
function generateReport() {
const statusPath = path.join('public', 'i18n-status', 'data', 'status.json');
if (!fs.existsSync(statusPath)) {
return 'No translation status data available.';
}
const status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
let report = '| Language | Done | Outdated | Missing | Progress |\n';
report += '|----------|------|----------|---------|----------|\n';
const languages = Object.entries(status.languages);
for (const [lang, data] of languages) {
if (lang === status.sourceLanguage) continue;
const progress = Math.round((data.done / data.total) * 100);
const emoji = progress >= 90 ? 'Done' : progress >= 70 ? 'Warning' : 'Error';
report += `| ${lang} | ${data.done} | ${data.outdated} | ${data.missing} | ${progress}% |\n`;
}
report += '\n[View Full Dashboard](/i18n-status/)';
return report;
}
console.log(generateReport());
Team Collaboration Workflow
Translation Contribution Guide
Create a TRANSLATING.md file:
# Translation Guide
Thank you for your interest in translating our documentation!
## How to Contribute
### 1. Check Current Status
Visit our [Translation Status Dashboard](/i18n-status/) to see which files need translation.
### 2. Claim a File
Before starting, check if anyone is already working on the file:
1. Search existing [Pull Requests](https://github.com/your-org/your-repo/pulls)
2. If no one is working on it, create an issue or comment on the dashboard
### 3. Translate
1. Fork the repository
2. Create a branch: `git checkout -b translate/zh-cn/introduction`
3. Copy the source file: `cp src/content/docs/en/introduction.md src/content/docs/zh-cn/introduction.md`
4. Translate the content
5. Test locally: `npm run dev`
### 4. Submit
1. Commit your changes: `git commit -m "Translate introduction to Chinese"`
2. Push to your fork: `git push origin translate/zh-cn/introduction`
3. Create a Pull Request
## Translation Guidelines
- **Preserve formatting**: Keep Markdown syntax intact
- **Keep code examples**: Usually, code doesn't need translation
- **Be consistent**: Use consistent terminology
- **Add context**: Use comments for difficult terms
## Need Help?
Join our [Discord](https://discord.gg/example) and ask in the #translations channel.
PR Template
Create .github/pull_request_template.md:
## Description
<!-- Describe your changes -->
## Type of Change
- [ ] Documentation update
- [ ] Translation
- [ ] New feature
- [ ] Bug fix
## Translation Checklist (if applicable)
- [ ] I have checked the [Translation Status Dashboard](/i18n-status/)
- [ ] I have preserved the original Markdown formatting
- [ ] I have tested the translation locally
- [ ] I have reviewed the terminology consistency
## Screenshots (if applicable)
<!-- Add screenshots to help explain your changes -->
## Additional Notes
<!-- Any additional information that reviewers should know -->
Monitoring and Maintenance
Health Check Script
// scripts/health-check.js
const fs = require('fs');
const path = require('path');
async function healthCheck() {
const issues = [];
// 1. Check configuration file
const configPath = 'lunaria.config.json';
if (!fs.existsSync(configPath)) {
issues.push('Missing lunaria.config.json');
}
// 2. Check language directories
const languages = ['en', 'zh-cn', 'ja', 'ko'];
for (const lang of languages) {
const langDir = `src/content/docs/${lang}`;
if (!fs.existsSync(langDir)) {
issues.push(`Missing language directory: ${langDir}`);
}
}
// 3. Check translation status
const statusPath = 'public/i18n-status/data/status.json';
if (fs.existsSync(statusPath)) {
const status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
for (const [lang, data] of Object.entries(status.languages)) {
if (lang === status.sourceLanguage) continue;
const progress = (data.done / data.total) * 100;
if (progress < 50) {
issues.push(`Low translation progress for ${lang}: ${progress.toFixed(0)}%`);
}
}
}
// Output results
if (issues.length === 0) {
console.log('All health checks passed!');
} else {
console.log('Health check found issues:\n');
issues.forEach(issue => console.log(issue));
process.exit(1);
}
}
healthCheck();
Periodic Maintenance Tasks
# .github/workflows/maintenance.yml
name: Maintenance Tasks
on:
schedule:
# Every Monday at 9 AM
- cron: '0 9 * * 1'
workflow_dispatch:
jobs:
report:
name: Generate Weekly Report
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run health check
run: node scripts/health-check.js
- name: Generate weekly report
id: report
run: |
REPORT=$(node scripts/weekly-report.js)
echo "report<<EOF" >> $GITHUB_OUTPUT
echo "$REPORT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create issue
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Weekly i18n Report - ' + new Date().toISOString().split('T')[0],
body: `${{ steps.report.outputs.report }}`,
labels: ['i18n', 'weekly-report']
});
Deployment and Release
Vercel Configuration
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"framework": "astro",
"trailingSlash": false,
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
]
}
]
}
Complete package.json Configuration
{
"name": "my-docs",
"type": "module",
"version": "1.0.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build && npm run lunaria:build",
"preview": "astro preview",
"astro": "astro",
"lint": "eslint src/",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lunaria:build": "lunaria build",
"lunaria:preview": "lunaria preview",
"i18n:check": "node scripts/health-check.js",
"i18n:report": "node scripts/weekly-report.js"
},
"dependencies": {
"@astrojs/starlight": "^0.26.0",
"@lunariajs/starlight": "^0.4.0",
"astro": "^5.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"eslint": "^8.57.0",
"prettier": "^3.2.0",
"typescript": "^5.3.0"
}
}
Series Summary and Outlook
LunariaJS Capabilities Overview
| Capability | Description | Chapter |
|---|---|---|
| Installation & Configuration | Quick project initialization | Part 2 |
| CLI Operations | Command line tool usage | Part 3 |
| Dashboard | Visual translation status | Part 4 |
| Git Integration | Version-based translation tracking | Part 5 |
| Starlight Integration | Documentation site integration | Part 6 |
| CI/CD Integration | Automated workflows | Part 7 |
| Core Architecture | Source code level understanding | Part 8 |
| Advanced Configuration | Enterprise customization | Part 9 |
| Complete Implementation | End-to-end practice | Part 10 |
Learning Path Review
Beginner Stage
|
+-- Part 1: Understand what LunariaJS is
+-- Part 2: Learn installation and configuration
+-- Part 3: Master CLI commands
+-- Part 4: Use the dashboard
+-- Part 5: Understand Git workflow
+-- Part 6: Integrate with Starlight
+-- Part 7: Configure CI/CD
|
v
Deep Dive Stage
|
+-- Part 8: Deep dive into source code architecture
+-- Part 9: Enterprise-level configuration
+-- Part 10: Complete implementation
|
v
Become a LunariaJS Expert!
Future Development Directions
As an active open source project, LunariaJS may have:
- More file format support: Excel, PO files, etc.
- Deeper CMS integration: Headless CMS support
- AI-assisted translation: Integration with machine translation services
- Real-time collaboration: Multi-person simultaneous editing
- Advanced analytics: Translation quality assessment
Recommended Community Resources
| Resource | Link |
|---|---|
| Official Documentation | https://lunaria.libdoc.top/ |
| Chinese Documentation | https://lunaria.libdoc.top/zh-cn/ |
| GitHub Repository | https://github.com/withastro/lunaria |
| Discord Community | https://discord.gg/astro |
| Astro Official Docs | https://docs.astro.build/ |
Conclusion
Congratulations on completing the complete learning journey from beginner to expert with LunariaJS!
Through this 10-article series, you have mastered:
- LunariaJS’s core concepts, installation, and configuration
- CLI commands and dashboard usage
- Git workflow and team collaboration
- Astro Starlight integration
- CI/CD automated deployment
- Core architecture and source code understanding
- Enterprise-level advanced configuration
- Complete project implementation
Now, you have the ability to build professional-grade multilingual documentation platforms using LunariaJS. Start your next project!
If you found this series helpful, please share it with more developers so more people can learn about LunariaJS, this powerful localization management tool.
Recommended Reading: