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
This commit is contained in:
alam00000
2026-01-14 02:31:44 +05:30
parent 59584813e4
commit 90346d7ea9
11 changed files with 1884 additions and 637 deletions

View File

@@ -14,6 +14,7 @@ export const supportedLanguages = [
'tr',
'id',
'it',
'pt',
] as const;
export type SupportedLanguage = (typeof supportedLanguages)[number];
@@ -23,17 +24,20 @@ export const languageNames: Record<SupportedLanguage, string> = {
de: 'Deutsch',
es: 'Español',
zh: '中文',
"zh-TW": '繁體中文(台灣)',
'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)(?:\/|$)/);
const langMatch = path.match(
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)(?:\/|$)/
);
if (
langMatch &&
supportedLanguages.includes(langMatch[1] as SupportedLanguage)
@@ -95,9 +99,12 @@ export const changeLanguage = (lang: SupportedLanguage): void => {
const currentLang = getLanguageFromUrl();
let newPath: string;
if (currentPath.match(/^\/(en|fr|de|zh|zh-TW|vi|tr|id|it)\//)) {
newPath = currentPath.replace(/^\/(en|fr|de|zh|zh-TW|vi|tr|id|it)\//, `/${lang}/`);
} else if (currentPath.match(/^\/(en|fr|de|zh|zh-TW|vi|tr|id|it)$/)) {
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}`;
@@ -161,7 +168,7 @@ export const rewriteLinks = (): void => {
return;
}
if (href.match(/^\/(en|fr|de|zh|zh-TW|vi|tr|id|it)\//)) {
if (href.match(/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)\//)) {
return;
}
let newHref: string;

View File

@@ -1,142 +1,157 @@
import {
supportedLanguages,
languageNames,
getLanguageFromUrl,
changeLanguage,
supportedLanguages,
languageNames,
getLanguageFromUrl,
changeLanguage,
} from './i18n';
export const createLanguageSwitcher = (): HTMLElement => {
const currentLang = getLanguageFromUrl();
const currentLang = getLanguageFromUrl();
const container = document.createElement('div');
container.className = 'relative';
container.id = 'language-switcher';
const container = document.createElement('div');
container.className = 'relative';
container.id = 'language-switcher';
const button = document.createElement('button');
button.className = `
const button = document.createElement('button');
button.className = `
inline-flex items-center gap-1.5 text-sm font-medium
bg-gray-800 text-gray-200 border border-gray-600
px-3 py-1.5 rounded-full transition-colors duration-200
shadow-sm hover:shadow-md hover:bg-gray-700
`.trim();
button.setAttribute('aria-haspopup', 'true');
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-haspopup', 'true');
button.setAttribute('aria-expanded', 'false');
const textSpan = document.createElement('span');
textSpan.className = 'font-medium';
textSpan.textContent = languageNames[currentLang];
const textSpan = document.createElement('span');
textSpan.className = 'font-medium';
textSpan.textContent = languageNames[currentLang];
const chevron = document.createElement('svg');
chevron.className = 'w-4 h-4';
chevron.setAttribute('fill', 'none');
chevron.setAttribute('stroke', 'currentColor');
chevron.setAttribute('viewBox', '0 0 24 24');
chevron.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>';
const chevron = document.createElement('svg');
chevron.className = 'w-4 h-4';
chevron.setAttribute('fill', 'none');
chevron.setAttribute('stroke', 'currentColor');
chevron.setAttribute('viewBox', '0 0 24 24');
chevron.innerHTML =
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>';
button.appendChild(textSpan);
button.appendChild(chevron);
button.appendChild(textSpan);
button.appendChild(chevron);
const dropdown = document.createElement('div');
dropdown.className = `
const dropdown = document.createElement('div');
dropdown.className = `
hidden absolute right-0 mt-2 w-40 rounded-lg
bg-gray-800 border border-gray-700 shadow-xl
py-1 z-50
`.trim();
dropdown.setAttribute('role', 'menu');
dropdown.setAttribute('role', 'menu');
supportedLanguages.forEach((lang) => {
const option = document.createElement('button');
option.className = `
supportedLanguages.forEach((lang) => {
const option = document.createElement('button');
option.className = `
w-full px-4 py-2 text-left text-sm text-gray-200
hover:bg-gray-700 flex items-center gap-2
${lang === currentLang ? 'bg-gray-700' : ''}
`.trim();
option.setAttribute('role', 'menuitem');
option.setAttribute('role', 'menuitem');
const name = document.createElement('span');
name.textContent = languageNames[lang];
const name = document.createElement('span');
name.textContent = languageNames[lang];
option.appendChild(name);
option.appendChild(name);
option.addEventListener('click', () => {
if (lang !== currentLang) {
changeLanguage(lang);
}
});
dropdown.appendChild(option);
option.addEventListener('click', () => {
if (lang !== currentLang) {
changeLanguage(lang);
}
});
container.appendChild(button);
container.appendChild(dropdown);
dropdown.appendChild(option);
});
button.addEventListener('click', (e) => {
e.stopPropagation();
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', (!isExpanded).toString());
dropdown.classList.toggle('hidden');
});
container.appendChild(button);
container.appendChild(dropdown);
document.addEventListener('click', () => {
button.setAttribute('aria-expanded', 'false');
dropdown.classList.add('hidden');
});
button.addEventListener('click', (e) => {
e.stopPropagation();
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', (!isExpanded).toString());
dropdown.classList.toggle('hidden');
});
return container;
document.addEventListener('click', () => {
button.setAttribute('aria-expanded', 'false');
dropdown.classList.add('hidden');
});
return container;
};
export const injectLanguageSwitcher = (): void => {
const footer = document.querySelector('footer');
if (!footer) return;
const simpleModeContainer = document.getElementById(
'simple-mode-language-switcher'
);
if (simpleModeContainer) {
const switcher = createLanguageSwitcher();
simpleModeContainer.appendChild(switcher);
return;
}
const headings = footer.querySelectorAll('h3');
let followUsColumn: HTMLElement | null = null;
const footer = document.querySelector('footer');
if (!footer) return;
headings.forEach((h3) => {
if (h3.textContent?.trim() === 'Follow Us' || h3.textContent?.trim() === 'Folgen Sie uns' || h3.textContent?.trim() === 'Theo dõi chúng tôi') {
followUsColumn = h3.parentElement;
}
});
const headings = footer.querySelectorAll('h3');
let followUsColumn: HTMLElement | null = null;
if (followUsColumn) {
const socialIconsContainer = followUsColumn.querySelector('.space-x-4');
headings.forEach((h3) => {
if (
h3.textContent?.trim() === 'Follow Us' ||
h3.textContent?.trim() === 'Folgen Sie uns' ||
h3.textContent?.trim() === 'Theo dõi chúng tôi'
) {
followUsColumn = h3.parentElement;
}
});
if (socialIconsContainer) {
const wrapper = document.createElement('div');
wrapper.className = 'inline-flex flex-col gap-4'; // gap-4 adds space between icons and switcher
if (followUsColumn) {
const socialIconsContainer = followUsColumn.querySelector('.space-x-4');
socialIconsContainer.parentNode?.insertBefore(wrapper, socialIconsContainer);
if (socialIconsContainer) {
const wrapper = document.createElement('div');
wrapper.className = 'inline-flex flex-col gap-4'; // gap-4 adds space between icons and switcher
wrapper.appendChild(socialIconsContainer);
const switcher = createLanguageSwitcher();
socialIconsContainer.parentNode?.insertBefore(
wrapper,
socialIconsContainer
);
switcher.className = 'relative w-full';
wrapper.appendChild(socialIconsContainer);
const switcher = createLanguageSwitcher();
const button = switcher.querySelector('button');
if (button) {
button.className = `
switcher.className = 'relative w-full';
const button = switcher.querySelector('button');
if (button) {
button.className = `
flex items-center justify-between w-full text-sm font-medium
bg-gray-800 text-gray-400 border border-gray-700
px-3 py-2 rounded-lg transition-colors duration-200
hover:text-white hover:border-gray-600
`.trim();
}
}
const dropdown = switcher.querySelector('div[role="menu"]');
if (dropdown) {
dropdown.classList.remove('mt-2', 'w-40');
dropdown.classList.add('bottom-full', 'mb-2', 'w-full');
}
const dropdown = switcher.querySelector('div[role="menu"]');
if (dropdown) {
dropdown.classList.remove('mt-2', 'w-40');
dropdown.classList.add('bottom-full', 'mb-2', 'w-full');
}
wrapper.appendChild(switcher);
} else {
const switcherContainer = document.createElement('div');
switcherContainer.className = 'mt-4 w-full';
const switcher = createLanguageSwitcher();
switcherContainer.appendChild(switcher);
followUsColumn.appendChild(switcherContainer);
}
wrapper.appendChild(switcher);
} else {
const switcherContainer = document.createElement('div');
switcherContainer.className = 'mt-4 w-full';
const switcher = createLanguageSwitcher();
switcherContainer.appendChild(switcher);
followUsColumn.appendChild(switcherContainer);
}
}
};