feat(i18n): implement full page internationalization for docs UI
- Add comprehensive UI translations object for EN, DE, FR - Translate page header, category labels, sidebar headings - Translate search button, GitHub section, all UI elements - Update category rendering to use translated labels - Display translated document titles from database in sidebar - Add updatePageUI function to apply translations dynamically - Update docs.html with IDs for dynamic translation - Language selector now updates entire page UI and document list All UI elements now fully support German and French translations.
This commit is contained in:
parent
223a0e4ac3
commit
b2f9bb2284
1 changed files with 217 additions and 22 deletions
|
|
@ -3,6 +3,192 @@ let currentDocument = null;
|
|||
let documentCards = null;
|
||||
let currentLanguage = 'en'; // Default language
|
||||
|
||||
// UI translations for full page i18n
|
||||
const UI_TRANSLATIONS = {
|
||||
en: {
|
||||
pageTitle: 'Framework Documentation',
|
||||
pageSubtitle: 'Technical specifications, guides, and reference materials',
|
||||
documentsHeading: 'Documents',
|
||||
searchButton: 'Search',
|
||||
backToDocuments: 'Back to Documents',
|
||||
selectDocument: 'Select a Document',
|
||||
selectDocumentDesc: 'Choose a document from the sidebar to begin reading',
|
||||
loadingDocument: 'Loading document...',
|
||||
errorLoadingDoc: 'Error loading document',
|
||||
tableOfContents: 'Table of Contents',
|
||||
downloadPdf: 'Download PDF',
|
||||
github: 'GitHub',
|
||||
publicRepository: 'Public Repository',
|
||||
publicRepositoryDesc: 'Source code, examples & contributions',
|
||||
readmeQuickStart: 'README & Quick Start',
|
||||
readmeQuickStartDesc: 'Installation and getting started guide',
|
||||
categories: {
|
||||
'getting-started': 'Getting Started',
|
||||
'resources': 'Resources',
|
||||
'research-theory': 'Research & Theory',
|
||||
'technical-reference': 'Technical Reference',
|
||||
'advanced-topics': 'Advanced Topics',
|
||||
'business-leadership': 'Business & Leadership'
|
||||
}
|
||||
},
|
||||
de: {
|
||||
pageTitle: 'Framework-Dokumentation',
|
||||
pageSubtitle: 'Technische Spezifikationen, Leitfäden und Referenzmaterialien',
|
||||
documentsHeading: 'Dokumente',
|
||||
searchButton: 'Suchen',
|
||||
backToDocuments: 'Zurück zu Dokumenten',
|
||||
selectDocument: 'Dokument auswählen',
|
||||
selectDocumentDesc: 'Wählen Sie ein Dokument aus der Seitenleiste, um mit dem Lesen zu beginnen',
|
||||
loadingDocument: 'Dokument wird geladen...',
|
||||
errorLoadingDoc: 'Fehler beim Laden des Dokuments',
|
||||
tableOfContents: 'Inhaltsverzeichnis',
|
||||
downloadPdf: 'PDF herunterladen',
|
||||
github: 'GitHub',
|
||||
publicRepository: 'Öffentliches Repository',
|
||||
publicRepositoryDesc: 'Quellcode, Beispiele und Beiträge',
|
||||
readmeQuickStart: 'README & Schnellstart',
|
||||
readmeQuickStartDesc: 'Installation und Einstiegsanleitung',
|
||||
categories: {
|
||||
'getting-started': 'Erste Schritte',
|
||||
'resources': 'Ressourcen',
|
||||
'research-theory': 'Forschung & Theorie',
|
||||
'technical-reference': 'Technische Referenz',
|
||||
'advanced-topics': 'Fortgeschrittene Themen',
|
||||
'business-leadership': 'Business & Führung'
|
||||
}
|
||||
},
|
||||
fr: {
|
||||
pageTitle: 'Documentation du Framework',
|
||||
pageSubtitle: 'Spécifications techniques, guides et matériels de référence',
|
||||
documentsHeading: 'Documents',
|
||||
searchButton: 'Rechercher',
|
||||
backToDocuments: 'Retour aux documents',
|
||||
selectDocument: 'Sélectionner un document',
|
||||
selectDocumentDesc: 'Choisissez un document dans la barre latérale pour commencer la lecture',
|
||||
loadingDocument: 'Chargement du document...',
|
||||
errorLoadingDoc: 'Erreur lors du chargement du document',
|
||||
tableOfContents: 'Table des matières',
|
||||
downloadPdf: 'Télécharger PDF',
|
||||
github: 'GitHub',
|
||||
publicRepository: 'Dépôt public',
|
||||
publicRepositoryDesc: 'Code source, exemples et contributions',
|
||||
readmeQuickStart: 'README & Démarrage rapide',
|
||||
readmeQuickStartDesc: 'Guide d\'installation et de démarrage',
|
||||
categories: {
|
||||
'getting-started': 'Premiers pas',
|
||||
'resources': 'Ressources',
|
||||
'research-theory': 'Recherche & Théorie',
|
||||
'technical-reference': 'Référence technique',
|
||||
'advanced-topics': 'Sujets avancés',
|
||||
'business-leadership': 'Business & Leadership'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Get current UI translations
|
||||
function getUITranslations(lang = currentLanguage) {
|
||||
return UI_TRANSLATIONS[lang] || UI_TRANSLATIONS.en;
|
||||
}
|
||||
|
||||
// Update all page UI elements with current language translations
|
||||
function updatePageUI(lang = currentLanguage) {
|
||||
const t = getUITranslations(lang);
|
||||
|
||||
// Update page header
|
||||
const pageTitle = document.querySelector('h1');
|
||||
if (pageTitle && pageTitle.textContent === UI_TRANSLATIONS.en.pageTitle ||
|
||||
pageTitle.textContent === UI_TRANSLATIONS.de.pageTitle ||
|
||||
pageTitle.textContent === UI_TRANSLATIONS.fr.pageTitle) {
|
||||
pageTitle.textContent = t.pageTitle;
|
||||
}
|
||||
|
||||
const pageSubtitle = document.querySelector('.text-gray-600.mt-2');
|
||||
if (pageSubtitle && (pageSubtitle.textContent === UI_TRANSLATIONS.en.pageSubtitle ||
|
||||
pageSubtitle.textContent === UI_TRANSLATIONS.de.pageSubtitle ||
|
||||
pageSubtitle.textContent === UI_TRANSLATIONS.fr.pageSubtitle)) {
|
||||
pageSubtitle.textContent = t.pageSubtitle;
|
||||
}
|
||||
|
||||
// Update search button
|
||||
const searchBtn = document.querySelector('#open-search-modal-btn span');
|
||||
if (searchBtn) {
|
||||
searchBtn.textContent = t.searchButton;
|
||||
}
|
||||
|
||||
// Update sidebar Documents heading
|
||||
const docsHeading = document.querySelector('aside h3');
|
||||
if (docsHeading && (docsHeading.textContent === UI_TRANSLATIONS.en.documentsHeading ||
|
||||
docsHeading.textContent === UI_TRANSLATIONS.de.documentsHeading ||
|
||||
docsHeading.textContent === UI_TRANSLATIONS.fr.documentsHeading)) {
|
||||
docsHeading.textContent = t.documentsHeading;
|
||||
}
|
||||
|
||||
// Update GitHub section heading
|
||||
const githubHeadings = document.querySelectorAll('aside h3');
|
||||
githubHeadings.forEach(heading => {
|
||||
if (heading.textContent.trim() === UI_TRANSLATIONS.en.github ||
|
||||
heading.textContent.trim() === UI_TRANSLATIONS.de.github ||
|
||||
heading.textContent.trim() === UI_TRANSLATIONS.fr.github) {
|
||||
// Keep the SVG icon, just update text
|
||||
const textNode = Array.from(heading.childNodes).find(node => node.nodeType === Node.TEXT_NODE);
|
||||
if (textNode) {
|
||||
textNode.textContent = t.github;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update GitHub links
|
||||
const githubLinks = document.querySelectorAll('aside a[href*="github.com"]');
|
||||
githubLinks.forEach(link => {
|
||||
const titleDiv = link.querySelector('.text-sm.font-medium');
|
||||
const descDiv = link.querySelector('.text-xs.text-gray-500');
|
||||
|
||||
if (titleDiv) {
|
||||
if (titleDiv.textContent === UI_TRANSLATIONS.en.publicRepository ||
|
||||
titleDiv.textContent === UI_TRANSLATIONS.de.publicRepository ||
|
||||
titleDiv.textContent === UI_TRANSLATIONS.fr.publicRepository) {
|
||||
titleDiv.textContent = t.publicRepository;
|
||||
} else if (titleDiv.textContent === UI_TRANSLATIONS.en.readmeQuickStart ||
|
||||
titleDiv.textContent === UI_TRANSLATIONS.de.readmeQuickStart ||
|
||||
titleDiv.textContent === UI_TRANSLATIONS.fr.readmeQuickStart) {
|
||||
titleDiv.textContent = t.readmeQuickStart;
|
||||
}
|
||||
}
|
||||
|
||||
if (descDiv) {
|
||||
if (descDiv.textContent === UI_TRANSLATIONS.en.publicRepositoryDesc ||
|
||||
descDiv.textContent === UI_TRANSLATIONS.de.publicRepositoryDesc ||
|
||||
descDiv.textContent === UI_TRANSLATIONS.fr.publicRepositoryDesc) {
|
||||
descDiv.textContent = t.publicRepositoryDesc;
|
||||
} else if (descDiv.textContent === UI_TRANSLATIONS.en.readmeQuickStartDesc ||
|
||||
descDiv.textContent === UI_TRANSLATIONS.de.readmeQuickStartDesc ||
|
||||
descDiv.textContent === UI_TRANSLATIONS.fr.readmeQuickStartDesc) {
|
||||
descDiv.textContent = t.readmeQuickStartDesc;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update Back to Documents button
|
||||
const backBtn = document.querySelector('#back-to-docs-btn span');
|
||||
if (backBtn) {
|
||||
backBtn.textContent = t.backToDocuments;
|
||||
}
|
||||
|
||||
// Update Select Document placeholder (if visible)
|
||||
const selectDocHeading = document.getElementById('select-document-heading');
|
||||
if (selectDocHeading) {
|
||||
selectDocHeading.textContent = t.selectDocument;
|
||||
}
|
||||
|
||||
const selectDocDesc = document.getElementById('select-document-desc');
|
||||
if (selectDocDesc) {
|
||||
selectDocDesc.textContent = t.selectDocumentDesc;
|
||||
}
|
||||
|
||||
// Update page title tag
|
||||
document.title = `${t.pageTitle} | Tractatus AI Safety`;
|
||||
}
|
||||
|
||||
// Initialize card-based viewer
|
||||
if (typeof DocumentCards !== 'undefined') {
|
||||
documentCards = new DocumentCards('document-content');
|
||||
|
|
@ -65,7 +251,6 @@ if (typeof window !== 'undefined') {
|
|||
// Document categorization - Final 5 categories (curated for public docs)
|
||||
const CATEGORIES = {
|
||||
'getting-started': {
|
||||
label: '📚 Getting Started',
|
||||
icon: '📚',
|
||||
description: 'Introduction, core concepts, and glossary',
|
||||
order: 1,
|
||||
|
|
@ -76,7 +261,6 @@ const CATEGORIES = {
|
|||
collapsed: false
|
||||
},
|
||||
'resources': {
|
||||
label: '📖 Resources',
|
||||
icon: '📖',
|
||||
description: 'Implementation guides and reference materials',
|
||||
order: 2,
|
||||
|
|
@ -87,7 +271,6 @@ const CATEGORIES = {
|
|||
collapsed: false
|
||||
},
|
||||
'research-theory': {
|
||||
label: '🔬 Research & Theory',
|
||||
icon: '🔬',
|
||||
description: 'Research papers, case studies, theoretical foundations',
|
||||
order: 3,
|
||||
|
|
@ -98,7 +281,6 @@ const CATEGORIES = {
|
|||
collapsed: true
|
||||
},
|
||||
'technical-reference': {
|
||||
label: '🔌 Technical Reference',
|
||||
icon: '🔌',
|
||||
description: 'API documentation, code examples, architecture',
|
||||
order: 4,
|
||||
|
|
@ -109,7 +291,6 @@ const CATEGORIES = {
|
|||
collapsed: true
|
||||
},
|
||||
'advanced-topics': {
|
||||
label: '🎓 Advanced Topics',
|
||||
icon: '🎓',
|
||||
description: 'Value pluralism, organizational theory, advanced concepts',
|
||||
order: 5,
|
||||
|
|
@ -120,7 +301,6 @@ const CATEGORIES = {
|
|||
collapsed: true
|
||||
},
|
||||
'business-leadership': {
|
||||
label: '💼 Business & Leadership',
|
||||
icon: '💼',
|
||||
description: 'Business cases, ROI analysis, executive briefs',
|
||||
order: 6,
|
||||
|
|
@ -188,6 +368,12 @@ function groupDocuments(docs) {
|
|||
function renderDocLink(doc, isHighlighted = false) {
|
||||
const highlightClass = isHighlighted ? 'text-blue-700 bg-blue-50 border border-blue-200' : '';
|
||||
|
||||
// Get translated title if available and language is not English
|
||||
let displayTitle = doc.title;
|
||||
if (currentLanguage !== 'en' && doc.translations && doc.translations[currentLanguage]) {
|
||||
displayTitle = doc.translations[currentLanguage].title || doc.title;
|
||||
}
|
||||
|
||||
// Determine if PDF download is available and get PDF path
|
||||
// First check if document has explicit download_formats.pdf
|
||||
let pdfPath = null;
|
||||
|
|
@ -213,7 +399,7 @@ function renderDocLink(doc, isHighlighted = false) {
|
|||
<div class="relative mb-1">
|
||||
<button class="doc-link w-full text-left px-3 py-2 ${paddingClass} rounded text-sm hover:bg-blue-50 transition ${highlightClass}"
|
||||
data-slug="${doc.slug}">
|
||||
<div class="font-medium text-gray-900">${doc.title}</div>
|
||||
<div class="font-medium text-gray-900">${displayTitle}</div>
|
||||
</button>
|
||||
${hasPDF ? `
|
||||
<a href="${pdfPath}"
|
||||
|
|
@ -253,11 +439,14 @@ async function loadDocuments() {
|
|||
const sortedCategories = Object.entries(CATEGORIES)
|
||||
.sort((a, b) => a[1].order - b[1].order);
|
||||
|
||||
const t = getUITranslations(currentLanguage);
|
||||
|
||||
sortedCategories.forEach(([categoryId, category]) => {
|
||||
const docs = grouped[categoryId] || [];
|
||||
if (docs.length === 0) return;
|
||||
|
||||
const isCollapsed = category.collapsed || false;
|
||||
const categoryLabel = t.categories[categoryId] || categoryId;
|
||||
|
||||
// Category header
|
||||
html += `
|
||||
|
|
@ -267,7 +456,7 @@ async function loadDocuments() {
|
|||
data-collapsed="${isCollapsed}">
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="category-icon">${category.icon}</span>
|
||||
<span>${category.label.replace(category.icon, '').trim()}</span>
|
||||
<span>${categoryLabel}</span>
|
||||
</span>
|
||||
<svg class="category-arrow w-5 h-5 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
|
|
@ -420,6 +609,8 @@ async function loadDocument(slug, lang = null) {
|
|||
try {
|
||||
isLoading = true;
|
||||
|
||||
const t = getUITranslations(language);
|
||||
|
||||
// Show loading state
|
||||
const contentEl = document.getElementById('document-content');
|
||||
contentEl.innerHTML = `
|
||||
|
|
@ -428,7 +619,7 @@ async function loadDocument(slug, lang = null) {
|
|||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="text-gray-600">Loading document...</p>
|
||||
<p class="text-gray-600">${t.loadingDocument}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -566,12 +757,13 @@ async function loadDocument(slug, lang = null) {
|
|||
|
||||
} catch (error) {
|
||||
console.error('Error loading document:', error);
|
||||
const t = getUITranslations(currentLanguage);
|
||||
document.getElementById('document-content').innerHTML = `
|
||||
<div class="text-center py-12">
|
||||
<svg class="h-12 w-12 text-red-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Error loading document</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">${t.errorLoadingDoc}</h3>
|
||||
<p class="text-sm text-gray-600">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -641,7 +833,7 @@ function closeToCModal() {
|
|||
// Initialize
|
||||
loadDocuments();
|
||||
|
||||
// Initialize language selector
|
||||
// Initialize language selector and detect initial language
|
||||
(function initLanguageSelector() {
|
||||
const selector = document.getElementById('language-selector');
|
||||
if (!selector) return;
|
||||
|
|
@ -651,8 +843,11 @@ loadDocuments();
|
|||
selector.value = initialLang;
|
||||
currentLanguage = initialLang;
|
||||
|
||||
// Update page UI with initial language
|
||||
updatePageUI(initialLang);
|
||||
|
||||
// Handle language change
|
||||
selector.addEventListener('change', (e) => {
|
||||
selector.addEventListener('change', async (e) => {
|
||||
const newLang = e.target.value;
|
||||
|
||||
// Save to localStorage
|
||||
|
|
@ -661,16 +856,16 @@ loadDocuments();
|
|||
// Update current language
|
||||
currentLanguage = newLang;
|
||||
|
||||
// Reload current document in new language
|
||||
if (currentDocument) {
|
||||
loadDocument(currentDocument.slug, newLang);
|
||||
} else {
|
||||
// If no document loaded yet, just update URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const currentDoc = urlParams.get('doc');
|
||||
if (currentDoc) {
|
||||
loadDocument(currentDoc, newLang);
|
||||
}
|
||||
// Update all page UI elements
|
||||
updatePageUI(newLang);
|
||||
|
||||
// Reload document list to show translated category labels and document titles
|
||||
const currentSlug = currentDocument ? currentDocument.slug : null;
|
||||
await loadDocuments();
|
||||
|
||||
// If a document was loaded, reload it in the new language
|
||||
if (currentSlug) {
|
||||
loadDocument(currentSlug, newLang);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue