Files
bentopdf/src/js/i18n/i18n.ts
alam00000 90346d7ea9 feat: Simple Mode language routing and translation improvements
## Simple Mode Enhancements
- Add `simple-index.html` as dedicated homepage for Simple Mode
- Hide marketing sections (FAQ, How It Works, Related Tools) on tool pages
- Add simplified navbar and footer for tool pages in Simple Mode
- Configure vite preview server to handle language-prefixed URLs

## Language Routing
- Add middleware to rewrite language-prefixed URLs (e.g., /de/merge-pdf.html)
- Support all languages: en, de, es, fr, id, it, pt, tr, vi, zh, zh-TW
- Create .htaccess with internal rewrites for Apache/Hostinger hosting

## Translation Updates
- Add missing translations for digitalSignPdf, validateSignaturePdf,
  emailToPdf, fontToOutline, deskewPdf to es, pt, tr, zh-TW
- Add Digital Signature and Validate Signature to homepage translation keys
- Fix language regex patterns to include all supported languages
- Fix typo in encrypt-pdf.html
2026-01-14 02:31:44 +05:30

190 lines
4.8 KiB
TypeScript

import i18next from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';
// Supported languages
export const supportedLanguages = [
'en',
'fr',
'de',
'es',
'zh',
'zh-TW',
'vi',
'tr',
'id',
'it',
'pt',
] as const;
export type SupportedLanguage = (typeof supportedLanguages)[number];
export const languageNames: Record<SupportedLanguage, string> = {
en: 'English',
fr: 'Français',
de: 'Deutsch',
es: 'Español',
zh: '中文',
'zh-TW': '繁體中文(台灣)',
vi: 'Tiếng Việt',
tr: 'Türkçe',
id: 'Bahasa Indonesia',
it: 'Italiano',
pt: 'Português',
};
export const getLanguageFromUrl = (): SupportedLanguage => {
const path = window.location.pathname;
const langMatch = path.match(
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)(?:\/|$)/
);
if (
langMatch &&
supportedLanguages.includes(langMatch[1] as SupportedLanguage)
) {
return langMatch[1] as SupportedLanguage;
}
const storedLang = localStorage.getItem('i18nextLng');
if (
storedLang &&
supportedLanguages.includes(storedLang as SupportedLanguage)
) {
return storedLang as SupportedLanguage;
}
return 'en';
};
let initialized = false;
export const initI18n = async (): Promise<typeof i18next> => {
if (initialized) return i18next;
const currentLang = getLanguageFromUrl();
await i18next
.use(HttpBackend)
.use(LanguageDetector)
.init({
lng: currentLang,
fallbackLng: 'en',
supportedLngs: supportedLanguages as unknown as string[],
ns: ['common', 'tools'],
defaultNS: 'common',
backend: {
loadPath: `${import.meta.env.BASE_URL.replace(/\/?$/, '/')}locales/{{lng}}/{{ns}}.json`,
},
detection: {
order: ['path', 'localStorage', 'navigator'],
lookupFromPathIndex: 0,
caches: ['localStorage'],
},
interpolation: {
escapeValue: false,
},
});
initialized = true;
return i18next;
};
export const t = (key: string, options?: Record<string, unknown>): string => {
return i18next.t(key, options);
};
export const changeLanguage = (lang: SupportedLanguage): void => {
if (!supportedLanguages.includes(lang)) return;
const currentPath = window.location.pathname;
const currentLang = getLanguageFromUrl();
let newPath: string;
if (currentPath.match(/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)\//)) {
newPath = currentPath.replace(
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)\//,
`/${lang}/`
);
} else if (currentPath.match(/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)$/)) {
newPath = `/${lang}`;
} else {
newPath = `/${lang}${currentPath}`;
}
const newUrl = newPath + window.location.search + window.location.hash;
window.location.href = newUrl;
};
// Apply translations to all elements with data-i18n attribute
export const applyTranslations = (): void => {
document.querySelectorAll('[data-i18n]').forEach((element) => {
const key = element.getAttribute('data-i18n');
if (key) {
const translation = t(key);
if (translation && translation !== key) {
element.textContent = translation;
}
}
});
document.querySelectorAll('[data-i18n-placeholder]').forEach((element) => {
const key = element.getAttribute('data-i18n-placeholder');
if (key && element instanceof HTMLInputElement) {
const translation = t(key);
if (translation && translation !== key) {
element.placeholder = translation;
}
}
});
document.querySelectorAll('[data-i18n-title]').forEach((element) => {
const key = element.getAttribute('data-i18n-title');
if (key) {
const translation = t(key);
if (translation && translation !== key) {
(element as HTMLElement).title = translation;
}
}
});
document.documentElement.lang = i18next.language;
};
export const rewriteLinks = (): void => {
const currentLang = getLanguageFromUrl();
if (currentLang === 'en') return;
const links = document.querySelectorAll('a[href]');
links.forEach((link) => {
const href = link.getAttribute('href');
if (!href) return;
if (
href.startsWith('http') ||
href.startsWith('mailto:') ||
href.startsWith('tel:') ||
href.startsWith('#') ||
href.startsWith('javascript:')
) {
return;
}
if (href.match(/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)\//)) {
return;
}
let newHref: string;
if (href.startsWith('/')) {
newHref = `/${currentLang}${href}`;
} else if (href.startsWith('./')) {
newHref = href.replace('./', `/${currentLang}/`);
} else if (href === '/' || href === '') {
newHref = `/${currentLang}/`;
} else {
newHref = `/${currentLang}/${href}`;
}
link.setAttribute('href', newHref);
});
};
export default i18next;