TRANSLATION.md
This guide will help you add new languages or improve existing translations for BentoPDF.
BentoPDF uses i18next for internationalization (i18n). Currently supported languages:
en) - Defaultbe)de)es)fr)it)pt)tr)vi)id)zh)zh-TW)ko)ru)The app automatically detects the language from the URL path:
/ or /en/ → English (default)/de/ → German/fr/ → FrenchBentoPDF uses a static pre-rendering approach for SEO-optimized i18n:
scripts/generate-i18n-pages.mjs generates localized HTML files in dist/{lang}/languageRouterPlugin in vite.config.ts handles URL rewritingTo improve existing translations:
public/locales/{language}/common.json and public/locales/{language}/tools.jsonTo add a new language (e.g., Japanese ja):
public/locales/en/ to public/locales/ja/ja/common.json and ja/tools.jsonsupportedLanguages and languageNames in src/js/i18n/i18n.ts'ja' to SUPPORTED_LANGUAGES in vite.config.tsnpm run build to generate static language pagesLet's add Spanish as an example:
# Create the directory
mkdir -p public/locales/es
# Copy the English template
cp public/locales/en/common.json public/locales/es/common.json
Open public/locales/es/common.json and translate all the values:
{
"nav": {
"home": "Inicio",
"about": "Acerca de",
"contact": "Contacto",
"allTools": "Todas las herramientas"
},
"hero": {
"title": "Tu conjunto de herramientas PDF gratuito y seguro",
"subtitle": "Combina, divide, comprime y edita archivos PDF directamente en tu navegador."
}
// ... continue translating all keys
}
⚠️ Important: Only translate the values, NOT the keys!
✅ Correct:
"home": "Inicio"
❌ Wrong:
"inicio": "Inicio"
Then do the same for public/locales/es/tools.json to translate all tool names and descriptions.
Edit src/js/i18n/i18n.ts:
// Add 'fr' to supported languages
export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi'] as const;
export type SupportedLanguage = (typeof supportedLanguages)[number];
// Add French display name
export const languageNames: Record<SupportedLanguage, string> = {
en: 'English',
de: 'Deutsch',
fr: 'Français', // ← Add this
};
In vite.config.ts, add your language to the SUPPORTED_LANGUAGES array:
const SUPPORTED_LANGUAGES = [
'en',
'de',
'es',
'zh',
'zh-TW',
'vi',
'it',
'id',
'tr',
'fr',
'pt',
'ja',
] as const;
Important: This is required for both dev server routing and the build-time i18n generation.
# Restart the dev server
npm run dev
# Visit the Japanese version
# http://localhost:5173/ja/
# Run build (includes i18n page generation)
npm run build
# Verify files were created
ls dist/ja/
# Should show: index.html, merge-pdf.html, etc.
The common.json file is organized into logical sections:
{
"nav": {
// Navigation menu items
},
"hero": {
// Homepage hero section
},
"features": {
// Features section
},
"tools": {
// Tool names and descriptions
},
"upload": {
// File upload UI
},
"settings": {
// Settings modal and keyboard shortcuts
},
"faq": {
// FAQ section
},
"footer": {
// Footer links and text
},
"compliance": {
// Security compliance information
},
"testimonials": {
// User testimonials
},
"support": {
// Support section
},
"alert": {
// Alert and error messages
}
}
"deletePage" not "delete_page""nav.home" is represented as:
{
"nav": {
"home": "Home"
}
}
"shortcutsWarning" is better than "warning1"data-i18n attribute)<!-- Translation key: nav.home -->
<a href="/" data-i18n="nav.home">Home</a>
The data-i18n attribute tells i18next which translation to use.
Tool names and descriptions are defined in src/js/config/tools.ts and use a special namespace:
{
name: 'Merge PDF', // Used for shortcuts only
subtitle: 'Combine multiple PDFs into one file.',
}
In translations:
{
"tools": {
"mergePdf": {
"name": "PDF zusammenführen",
"subtitle": "Mehrere PDFs in eine Datei kombinieren."
}
}
}
t() function)For translations that need to be applied dynamically:
import { t } from './i18n/i18n';
const message = t('alert.error');
console.log(message); // "Error" or "Fehler" depending on language
For input placeholders:
<input
type="text"
placeholder="Search for a tool..."
data-i18n-placeholder="tools.searchPlaceholder"
/>
In common.json:
{
"tools": {
"searchPlaceholder": "Nach einem Tool suchen..."
}
}
Start development server:
npm run dev
Visit each language:
http://localhost:5173/en/http://localhost:5173/de/http://localhost:5173/vi/http://localhost:5173/id/http://localhost:5173/zh/http://localhost:5173/zh-TW/http://localhost:5173/fr/http://localhost:5173/es/Check these pages:
/)/about.html)/contact.html)/faq.html)/merge-pdf.html)Test these interactions:
Check for missing translations:
# This will show any missing keys
node scripts/check-translations.js
(If this script doesn't exist, you may need to create it or manually compare JSON files)
Test in different browsers:
BentoPDF is friendly, clear, and professional. Match this tone in your translations.
✅ Good:
"hero.title": "Ihr kostenloses und sicheres PDF-Toolkit"
❌ Too formal:
"hero.title": "Ihr gebührenfreies und gesichertes Werkzeug für PDF-Dokumente"
Some strings contain HTML or special characters:
{
"faq.analytics.answer": "We care about your privacy. BentoPDF does not track personal information. We use <a href=\"https://simpleanalytics.com\" class=\"text-indigo-400 hover:underline\" target=\"_blank\" rel=\"noopener noreferrer\">Simple Analytics</a> solely to see anonymous visit counts."
}
When translating, keep the HTML tags intact:
{
"faq.analytics.answer": "Wir schätzen Ihre Privatsphäre. BentoPDF verfolgt keine persönlichen Informationen. Wir verwenden <a href=\"https://simpleanalytics.com\" class=\"text-indigo-400 hover:underline\" target=\"_blank\" rel=\"noopener noreferrer\">Simple Analytics</a> ausschließlich, um anonyme Besucherzahlen zu sehen."
}
If your language has complex plural rules or gender distinctions, consult the i18next pluralization guide.
Example:
{
"pages": "page",
"pages_plural": "pages"
}
Keep these as-is:
For technical terms, use commonly accepted translations in your language:
If unsure, check how other PDF tools translate these terms in your language.
Some UI elements have limited space. Try to keep translations similar in length to the English version.
If a translation is much longer, test it visually to ensure it doesn't break the layout.
Solution:
Possible causes:
data-i18n attribute in HTMLSolution:
en/common.json to find missing keysSymptoms:
SyntaxError: Unexpected token } in JSON at position 1234
Solution:
\")Solution:
Make sure you added the language to both arrays in i18n.ts:
export const supportedLanguages = ['en', 'de', 'es', 'fr', 'zh', 'vi']; // ← Add here
export const languageNames = {
en: 'English',
de: 'Deutsch',
es: 'Español',
fr: 'Français', // ← And here
zh: '中文',
vi: 'Tiếng Việt',
};
Symptoms:
Visiting http://localhost:5173/ja/about.html shows a 404 error page.
Solution:
You need to add your language code to SUPPORTED_LANGUAGES in vite.config.ts:
const SUPPORTED_LANGUAGES = [
'en',
'de',
'es',
'zh',
'zh-TW',
'vi',
'it',
'id',
'tr',
'fr',
'pt',
'ja',
] as const;
After updating, restart the dev server:
npm run dev
When adding a new language, make sure these files are updated:
public/locales/{lang}/common.json - Main translation filepublic/locales/{lang}/tools.json - Tools translation filesrc/js/i18n/i18n.ts - Add to supportedLanguages and languageNamesvite.config.ts - Add to SUPPORTED_LANGUAGES array/{lang}/)npm run build and verify dist/{lang}/ folder is createdIf you have questions or need help:
public/locales/de/common.json for referenceOnce you've completed a translation:
git checkout -b add-french-translationgit commit -m "Add French translation"git push origin add-french-translationThank you for contributing to BentoPDF! 🎉
Current translation coverage:
| Language | Code | Status | Maintainer |
|---|---|---|---|
| English | en | ✅ Complete | Core team |
| German | de | ✅ Complete | Community |
| Spanish | es | ✅ Complete | Community |
| French | fr | ✅ Complete | Community |
| Italian | it | ✅ Complete | Community |
| Portuguese | pt | ✅ Complete | Community |
| Turkish | tr | ✅ Complete | Community |
| Vietnamese | vi | ✅ Complete | Community |
| Indonesian | id | ✅ Complete | Community |
| Chinese | zh | ✅ Complete | Community |
| Traditional Chinese | zh-TW | ✅ Complete | Community |
| Korean | ko | ✅ Complete | Community |
| Russian | ru | ✅ Complete | Community |
| Your Language | ?? | 🚧 In Progress | You? |
Last Updated: January 2026