# Multilingual Support Design ## Context The site needs to support both German and English content with seamless language switching. Hugo provides built-in multilingual support that we'll leverage. The current content is in German only, stored directly in `/content/`. ## Goals / Non-Goals ### Goals - Support German (de) and English (en) languages - Automatic browser language detection for first-time visitors - Persistent language preference via URL structure - Language switcher that maintains page context (e.g., `/de/cv` ↔ `/en/cv`) - All existing content translated to English - Clean URL structure using language codes as path prefixes ### Non-Goals - Translation management system or CMS integration - More than 2 languages at this stage - Client-side JavaScript for language switching (Hugo server-side only) - Automated translation (manual translation required) ## Decisions ### Decision 1: Hugo Multilingual Mode **Approach**: Use Hugo's built-in multilingual mode with language code prefixes in URLs **Rationale**: - Hugo natively supports multilingual sites with `defaultContentLanguage` and `[languages]` config - URL structure `/de/` and `/en/` clearly indicates language and persists preference - No cookies or JavaScript needed for language persistence - SEO-friendly with proper `hreflang` tags **Alternatives Considered**: - Subdomain approach (de.markusgraf.ch, en.markusgraf.ch): Rejected due to complexity and DNS configuration - Client-side JavaScript switching: Rejected to keep site static and performant ### Decision 2: Content Organization **Approach**: Reorganize content into language-specific directories `/content/de/` and `/content/en/` **Structure**: ``` content/ ├── de/ │ ├── _index.md │ └── cv.md └── en/ ├── _index.md └── cv.md ``` **Rationale**: - Clear separation of language-specific content - Hugo automatically links translations when file paths match - Easy to maintain and scale ### Decision 3: Language Switcher Implementation **Approach**: Add language switcher in navigation partial using Hugo's `.Translations` function **Implementation**: - Display language switcher as links (e.g., "DE | EN") - Active language styled differently - Maintains current page context when switching - Falls back to home page if translation doesn't exist **Rationale**: - Simple, no JavaScript required - Hugo provides `.Translations` to find corresponding pages - Consistent with existing navigation patterns ### Decision 4: Default Language **Approach**: German (de) as default with English (en) as secondary **Configuration**: ```toml defaultContentLanguage = "de" defaultContentLanguageInSubdir = true ``` **Rationale**: - Existing content is in German - Primary audience is German-speaking - `defaultContentLanguageInSubdir = true` ensures consistent URLs (`/de/` for German, not just `/`) ### Decision 5: Browser Language Detection **Approach**: Hugo serves content based on URL path; Apache/Nginx handles initial redirect based on `Accept-Language` header **Rationale**: - Hugo is a static site generator and cannot detect browser language at runtime - Server-side redirect is the cleanest approach - Falls back to default language (German) if header not present - Once user navigates, URL path maintains their choice **Migration Path**: 1. Configure multilingual mode in hugo.toml 2. Reorganize existing content to `/content/de/` 3. Create English translations in `/content/en/` 4. Update templates with language switcher 5. Configure server redirect rules (optional, for initial visit only) ## Risks / Trade-offs ### Risk 1: Breaking Change in Content Structure **Risk**: Moving content files breaks existing local references and workflows **Mitigation**: - Clear documentation of new structure - All content moved in single commit - Update paths in OpenSpec documentation ### Risk 2: Translation Maintenance **Risk**: Keeping German and English content in sync over time **Mitigation**: - Document translation process - Consider adding translation status tracking in future - For now, manual process with clear ownership ### Risk 3: SEO Impact **Risk**: URL structure changes may affect search rankings **Mitigation**: - Implement proper `hreflang` tags (Hugo does this automatically) - Add redirects from old URLs to new German URLs - Submit new sitemap to search engines ## Migration Plan ### Phase 1: Configure Hugo (No Breaking Changes Yet) 1. Update hugo.toml with multilingual configuration 2. Test configuration with existing content structure ### Phase 2: Restructure Content 1. Move existing content to `/content/de/` 2. Update any internal references 3. Build and verify German site works at `/de/` ### Phase 3: Add English Content 1. Create `/content/en/` directory 2. Translate home page content 3. Translate CV page content 4. Translate navigation and UI strings ### Phase 4: Update Templates 1. Add language switcher to navigation 2. Update partials for multilingual strings 3. Test language switching functionality ### Phase 5: Server Configuration (Optional) 1. Add .htaccess or nginx rules for browser language detection 2. Redirect root `/` to `/de/` or `/en/` based on Accept-Language header ## Open Questions **Q: Should the root URL `/` redirect to a language version, or show a language selection page?** A: Redirect to `/de/` as default, optionally detect browser language. No standalone language selection page needed. **Q: How should we handle the menu configuration for different languages?** A: Hugo supports language-specific menus using `[[languages.de.menu.main]]` and `[[languages.en.menu.main]]` in hugo.toml. **Q: Should we translate the project descriptions on the home page?** A: Yes, translate all visible content including project descriptions. ## Implementation Bugs and Solutions During implementation, several critical bugs were discovered and fixed. These are documented here to avoid future pitfalls. ### Bug 1: Language Switcher Showing Current Language **Problem**: The language switcher initially showed a dropdown with the current language as the toggle button (e.g., showing "Deutsch" when on German pages). This was confusing because users couldn't easily see which language to switch to. **Solution**: Simplified the language switcher to only show the alternative language as a direct link. Removed the dropdown pattern and changed from: ```html ``` To: ```html {{ if .IsTranslated }} {{ range .Translations }} {{ end }} {{ end }} ``` **Lesson**: For a two-language site, showing only the alternative language is clearer than a dropdown showing the current language. ### Bug 2: Language Switcher Not Maintaining Page Context **Problem**: When switching languages from `/de/cv/` to English, it would redirect to `/en/` (home) instead of `/en/cv/` (maintaining the CV page context). **Solution**: Added `translationKey` to the frontmatter of both language versions of each content file to explicitly link them as translations: ```yaml --- title: "Curriculum Vitae" translationKey: "cv" --- ``` This allows Hugo's `.IsTranslated` and `.Translations` functions to correctly find corresponding pages. **Lesson**: Hugo doesn't automatically recognize translations by filename alone when using language subdirectories. Always add `translationKey` to explicitly link translated pages. ### Bug 3: CV Pages Rendering with Wrong Language Context **Problem**: The most critical bug - CV pages would render with the wrong language: - `/en/cv/` would show `lang="de"`, German menu ("Lebenslauf"), and German language context - Both German and English CV pages would show incorrect language switcher labels **Root Cause**: Using language subdirectories (`content/de/cv.md` and `content/en/cv.md`) caused Hugo to incorrectly resolve the language context for these pages, even though `hugo list all` showed them with the correct language tags. **Solution**: Reorganized content files to use Hugo's language suffix naming convention instead of subdirectories: - Changed from: `content/de/cv.md` and `content/en/cv.md` - Changed to: `content/cv.de.md` and `content/cv.en.md` After this change, all pages rendered with the correct language context. **Lesson**: With Hugo's multilingual mode and `defaultContentLanguageInSubdir = true`, use the `.{lang}.md` suffix pattern (e.g., `filename.de.md`, `filename.en.md`) rather than language subdirectories (`de/filename.md`, `en/filename.md`). The subdirectory approach can cause template rendering issues where the wrong language context is used. ### Bug 4: Home and Navbar Brand Not Respecting Language **Problem**: Clicking the "Home" link or navbar brand from `/en/cv/` would always redirect to `/` (German home) instead of `/en/` (English home), breaking language persistence. **Root Cause**: Both links used `{{ .Site.BaseURL }}` which always points to the absolute root URL without language awareness. **Solution**: Changed both to use Hugo's `relLangURL` function: ```html Home Home ``` This makes the links language-aware, so: - On German pages: `{{ "/" | relLangURL }}` → `/de/` - On English pages: `{{ "/" | relLangURL }}` → `/en/` **Lesson**: Never use `{{ .Site.BaseURL }}` for internal navigation in multilingual sites. Always use `relLangURL` or `absLangURL` to maintain language context. ### Bug 5: Menu URLs Not Language-Aware **Problem**: The German "Lebenslauf" menu link pointed to `/cv/` which resulted in a 404 error because with `defaultContentLanguageInSubdir = true`, German pages are at `/de/cv/`, not `/cv/`. **Root Cause**: Menu URLs in `hugo.toml` were hardcoded: ```toml [[languages.de.menu.main]] url = '/cv/' # Wrong - page doesn't exist here ``` **Solution**: Updated menu URLs to include the language prefix: ```toml [[languages.de.menu.main]] name = 'Lebenslauf' url = '/de/cv/' # Correct weight = 10 [[languages.en.menu.main]] name = 'Curriculum Vitae' url = '/en/cv/' # Correct weight = 10 ``` **Lesson**: When `defaultContentLanguageInSubdir = true`, even the default language requires the language prefix in all URLs. Don't assume the default language is accessible at the root paths. ## Best Practices Derived from Bugs 1. **Content Organization**: Use `filename.{lang}.md` pattern instead of `{lang}/filename.md` subdirectories to avoid template context issues. 2. **Translation Linking**: Always add `translationKey` to frontmatter for explicit translation relationships. 3. **URL Generation**: Use Hugo's language-aware functions: - `relLangURL` for relative URLs - `absLangURL` for absolute URLs - `.RelPermalink` for page permalinks - Never use `{{ .Site.BaseURL }}` for internal links 4. **Menu Configuration**: When `defaultContentLanguageInSubdir = true`, all menu URLs must include the language prefix, even for the default language. 5. **Testing**: Always test all pages in all languages, not just home pages. CV/subpages may render with different template contexts than home pages.