This guide will help you add new languages or improve existing translations for BentoPDF.
- Overview
- Quick Start
- Adding a New Language
- Translation File Structure
- Where Translations Are Used
- Testing Your Translations
- Translation Guidelines
- Common Issues
BentoPDF uses i18next for internationalization (i18n). Currently supported languages:
- English (
en) - Default - Belarusian (
be) - German (
de) - Spanish (
es) - French (
fr) - Italian (
it) - Portuguese (
pt) - Turkish (
tr) - Vietnamese (
vi) - Indonesian (
id) - Chinese (
zh) - Traditional Chinese (Taiwan) (
zh-TW)
The app automatically detects the language from the URL path:
/or/en/→ English (default)/de/→ German/fr/→ French- etc.
BentoPDF uses a static pre-rendering approach for SEO-optimized i18n:
- Build time:
scripts/generate-i18n-pages.mjsgenerates localized HTML files indist/{lang}/ - Dev/Preview:
languageRouterPlugininvite.config.tshandles URL rewriting - Production: Nginx serves static files directly from language directories
To improve existing translations:
- Navigate to
public/locales/{language}/common.jsonandpublic/locales/{language}/tools.json - Find the key you want to update
- Change the translation value
- Save and test
To add a new language (e.g., Japanese ja):
- Copy
public/locales/en/topublic/locales/ja/ - Translate all values in both
ja/common.jsonandja/tools.json - Add Japanese to
supportedLanguagesandlanguageNamesinsrc/js/i18n/i18n.ts - Add
'ja'toSUPPORTED_LANGUAGESinvite.config.ts - Restart the dev server
- Run
npm run buildto generate static language pages - Test thoroughly
Let'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.jsonOpen 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
}✅ Correct:
"home": "Inicio"❌ Wrong:
"inicio": "Inicio"Then do the same for public/locales/fr/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
}
}- Use camelCase for keys:
"deletePage"not"delete_page" - Use nested objects for organization:
"nav.home"is represented as:{ "nav": { "home": "Home" } } - Be descriptive:
"shortcutsWarning"is better than"warning1"
<!-- 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."
}
}
}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 languageFor 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:
- English:
http://localhost:5173/en/ - German:
http://localhost:5173/de/ - Vietnamese:
http://localhost:5173/vi/ - Indonesian:
http://localhost:5173/id/ - Chinese:
http://localhost:5173/zh/ - Traditional Chinese (Taiwan):
http://localhost:5173/zh-TW/ - French:
http://localhost:5173/fr/ - Your new language:
http://localhost:5173/es/
- English:
-
Check these pages:
- Homepage (
/) - About page (
/about.html) - Contact page (
/contact.html) - FAQ page (
/faq.html) - Tool pages (e.g.,
/merge-pdf.html)
- Homepage (
-
Test these interactions:
- Click the language switcher in the footer
- Navigate between pages
- Open the settings modal (click gear icon next to search)
- Try a tool to see upload messages
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:
- Chrome/Edge
- Firefox
- Safari
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:
- BentoPDF
- GitHub
- Discord
- Chrome, Firefox, Safari, etc.
- Terms and Conditions
- Privacy Policy
- Licensing
For technical terms, use commonly accepted translations in your language:
- "Merge" → "Fusionner" (French), "Zusammenführen" (German)
- "Split" → "Diviser" (French), "Teilen" (German)
- "Compress" → "Compresser" (French), "Komprimieren" (German)
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:
- Clear your browser cache
- Hard refresh (Ctrl+F5 or Cmd+Shift+R)
- Check browser console for errors
- Verify the JSON file is valid (no syntax errors)
Possible causes:
- Missing translation key in your language file
- Missing
data-i18nattribute in HTML - Hardcoded text in JavaScript
Solution:
- Compare your language file with
en/common.jsonto find missing keys - Search the codebase for hardcoded strings
Symptoms:
SyntaxError: Unexpected token } in JSON at position 1234
Solution:
- Use a JSON validator: https://jsonlint.com/
- Common mistakes:
- Trailing comma after last item
- Missing or extra quotes
- Unescaped quotes inside strings (use
\")
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 devWhen adding a new language, make sure these files are updated:
-
public/locales/{lang}/common.json- Main translation file -
public/locales/{lang}/tools.json- Tools translation file -
src/js/i18n/i18n.ts- Add tosupportedLanguagesandlanguageNames -
vite.config.ts- Add toSUPPORTED_LANGUAGESarray - Test all pages: homepage, about, contact, FAQ, tool pages
- Test settings modal and shortcuts
- Test language switcher in footer
- Verify URL routing works (
/{lang}/) - Run
npm run buildand verifydist/{lang}/folder is created - Test that all tools load correctly
If you have questions or need help:
- Check existing translations in
public/locales/de/common.jsonfor reference - Open an issue on GitHub
- Join our Discord server
Once you've completed a translation:
- Test thoroughly (see Testing Your Translations)
- Fork the repository on GitHub
- Create a new branch:
git checkout -b add-french-translation - Commit your changes:
git commit -m "Add French translation" - Push to your fork:
git push origin add-french-translation - Open a Pull Request with:
- Description of the language added
- Screenshots showing the translation in action
- Confirmation that you've tested all pages
Thank 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 |
| Your Language | ?? |
🚧 In Progress | You? |
Last Updated: January 2026