|
| 1 | +# Multi-Language Research — Astro 6 + SEO Best Practices 2026 |
| 2 | + |
| 3 | +> Date: 2026-04-08 |
| 4 | +> Astro Version: 6.1.5 (upgraded from 5.17.1) |
| 5 | +
|
| 6 | +## Industry Consensus: Subdirectory Approach |
| 7 | + |
| 8 | +All major sources agree — subdirectory (`/tr/`, `/de/`) is the standard for 85%+ of websites. |
| 9 | + |
| 10 | +| Source | Recommendation | |
| 11 | +|---|---| |
| 12 | +| Google Search Central | Subdirectory for unified authority | |
| 13 | +| Next.js App Router | `[locale]/` dynamic segment | |
| 14 | +| Astro 6 | `src/pages/{locale}/` folder structure | |
| 15 | +| Vercel | Subdirectory for SEO-optimized sites | |
| 16 | +| SEO Industry (2026) | Subdirectory dominates | |
| 17 | + |
| 18 | +### Why Subdirectory Wins |
| 19 | +- Domain authority consolidation (backlinks benefit all languages) |
| 20 | +- Single Google Search Console property |
| 21 | +- Low maintenance (one hosting, one SSL, one deploy) |
| 22 | +- Easy hreflang management |
| 23 | + |
| 24 | +### Google Critical Warnings |
| 25 | +1. **"Avoid automatically redirecting users from one language version to another"** — harmful for both users and crawlers |
| 26 | +2. **"Don't use IP analysis to adapt your content"** — unreliable |
| 27 | +3. Google uses **visible page content** to determine language, not `lang` attribute or URL |
| 28 | +4. Recommend **hyperlinks to other language versions** for user choice |
| 29 | + |
| 30 | +## Astro 6 Native i18n System |
| 31 | + |
| 32 | +### Configuration (`astro.config.mjs`) |
| 33 | + |
| 34 | +```javascript |
| 35 | +export default defineConfig({ |
| 36 | + site: 'https://intellica.net', |
| 37 | + output: 'static', |
| 38 | + i18n: { |
| 39 | + locales: ['en', 'tr'], |
| 40 | + defaultLocale: 'en', |
| 41 | + routing: { |
| 42 | + prefixDefaultLocale: false, // EN at /, TR at /tr/ |
| 43 | + }, |
| 44 | + fallback: { |
| 45 | + tr: 'en' // Missing TR pages fall back to EN |
| 46 | + } |
| 47 | + }, |
| 48 | + integrations: [ |
| 49 | + sitemap({ |
| 50 | + i18n: { |
| 51 | + defaultLocale: 'en', |
| 52 | + locales: { en: 'en-US', tr: 'tr-TR' }, |
| 53 | + }, |
| 54 | + }), |
| 55 | + ], |
| 56 | +}); |
| 57 | +``` |
| 58 | + |
| 59 | +### File Structure |
| 60 | + |
| 61 | +``` |
| 62 | +src/pages/ |
| 63 | + index.astro → intellica.net/ |
| 64 | + about.astro → intellica.net/about/ |
| 65 | + products.astro → intellica.net/products/ |
| 66 | + tr/ |
| 67 | + index.astro → intellica.net/tr/ |
| 68 | + about.astro → intellica.net/tr/about/ |
| 69 | + products.astro → intellica.net/tr/products/ |
| 70 | +``` |
| 71 | + |
| 72 | +### Helper Functions (`astro:i18n`) |
| 73 | + |
| 74 | +```javascript |
| 75 | +import { |
| 76 | + getRelativeLocaleUrl, // getRelativeLocaleUrl('tr', 'about') → '/tr/about' |
| 77 | + getAbsoluteLocaleUrl, // Full URL with domain |
| 78 | + getRelativeLocaleUrlList, // All locale variants for a path |
| 79 | + getLocaleByPath, // Extract locale from URL path |
| 80 | +} from 'astro:i18n'; |
| 81 | +``` |
| 82 | + |
| 83 | +### Page-level Locale Access |
| 84 | + |
| 85 | +- `Astro.currentLocale` — Works in static. Returns locale from URL or defaultLocale. |
| 86 | +- `Astro.preferredLocale` — SSR only. NOT available in static builds. |
| 87 | +- `Astro.preferredLocaleList` — SSR only. |
| 88 | + |
| 89 | +### Translation System (Official Recipe) |
| 90 | + |
| 91 | +```typescript |
| 92 | +// src/i18n/ui.ts |
| 93 | +export const ui = { |
| 94 | + en: { 'nav.home': 'Home', 'nav.about': 'About' }, |
| 95 | + tr: { 'nav.home': 'Ana Sayfa', 'nav.about': 'Hakkımızda' }, |
| 96 | +} as const; |
| 97 | + |
| 98 | +// src/i18n/utils.ts |
| 99 | +export function getLangFromUrl(url: URL) { ... } |
| 100 | +export function useTranslations(lang) { ... } |
| 101 | +export function useTranslatedPath(lang) { ... } |
| 102 | +``` |
| 103 | + |
| 104 | +### SEO: hreflang Tags |
| 105 | + |
| 106 | +```html |
| 107 | +<link rel="alternate" hreflang="en" href="https://intellica.net/about" /> |
| 108 | +<link rel="alternate" hreflang="tr" href="https://intellica.net/tr/about" /> |
| 109 | +<link rel="alternate" hreflang="x-default" href="https://intellica.net/about" /> |
| 110 | +``` |
| 111 | + |
| 112 | +### Sitemap: Automatic hreflang XML |
| 113 | + |
| 114 | +`@astrojs/sitemap` with i18n config auto-generates: |
| 115 | +```xml |
| 116 | +<url> |
| 117 | + <loc>https://intellica.net/about/</loc> |
| 118 | + <xhtml:link rel="alternate" hreflang="en-US" href="https://intellica.net/about/"/> |
| 119 | + <xhtml:link rel="alternate" hreflang="tr-TR" href="https://intellica.net/tr/about/"/> |
| 120 | +</url> |
| 121 | +``` |
| 122 | + |
| 123 | +### Astro 6 Breaking Change |
| 124 | +- `redirectToDefaultLocale` default changed from `true` (v5) to `false` (v6) |
| 125 | +- Requires `prefixDefaultLocale: true` to function |
| 126 | +- Not relevant if using `prefixDefaultLocale: false` |
| 127 | + |
| 128 | +## Browser Language Detection — Static Site Limitations |
| 129 | + |
| 130 | +`Astro.preferredLocale` is SSR-only. For static sites, options: |
| 131 | + |
| 132 | +1. **Client-side JS** — Read `navigator.language`, show banner (NOT redirect) |
| 133 | +2. **Language switcher in header** — Always visible toggle |
| 134 | +3. **Combination** — Toggle always present + soft banner for detected language mismatch |
| 135 | + |
| 136 | +Google recommends option 2+3: **always provide a language switcher, optionally suggest based on browser language, never force redirect.** |
| 137 | + |
| 138 | +## Third-Party Libraries — Not Recommended |
| 139 | + |
| 140 | +| Library | Status (2026) | |
| 141 | +|---|---| |
| 142 | +| astro-i18next | Abandoned, Astro 5 incompatible | |
| 143 | +| astro-i18n-aut | Inactive 1+ year | |
| 144 | +| Paraglide JS | SSR only, not for static | |
| 145 | +| **Astro built-in** | **Stable, recommended** | |
| 146 | + |
| 147 | +## Current Site State |
| 148 | + |
| 149 | +- All content hardcoded in .astro files (no content collections) |
| 150 | +- No existing i18n infrastructure |
| 151 | +- Organization schema already declares `availableLanguage: ["English", "Turkish"]` |
| 152 | +- Navigation links hardcoded as arrays in Header/Footer/BottomNav components |
| 153 | +- 34 pages, static output, GitHub Pages deploy |
| 154 | + |
| 155 | +## Key Decisions Needed |
| 156 | + |
| 157 | +1. Default language: EN (preserves current Google index) vs TR (company is Turkish) |
| 158 | +2. Content extraction: How to organize translation strings |
| 159 | +3. Browser detection: Banner vs redirect vs toggle-only |
| 160 | +4. Page duplication: Full duplicate pages vs shared templates with translated content |
| 161 | +5. Redirect strategy: Old `/tr/*` URLs → new `/tr/*` URLs |
0 commit comments