requestPath); $currentLang = $defaultLang; // Remove language prefix from URL if present if (!empty($pathParts[0]) && in_array($pathParts[0], $availableLangs) && $pathParts[0] !== $defaultLang) { $currentLang = array_shift($pathParts); $reflection = new ReflectionProperty($ctx, 'requestPath'); $reflection->setValue($ctx, implode('/', $pathParts)); } $ctx->set('currentLang', $currentLang); $ctx->set('defaultLang', $defaultLang); $ctx->set('availableLangs', $availableLangs); $ctx->set('langPrefix', $currentLang !== $defaultLang ? "/$currentLang" : ''); $ctx->set('translations', loadTranslations($currentLang)); return $ctx; }); // Filter content and metadata by language Hooks::add(Hook::PROCESS_CONTENT, function(mixed $data, string $dirOrType, string $extraContext = '') { global $GLOBALS; $ctx = $GLOBALS['ctx'] ?? null; if (!$ctx) return $data; $currentLang = $ctx->get('currentLang', 'en'); // Merge language-specific metadata sections if ($extraContext === 'metadata' && isset($data['_raw'][$currentLang]) && is_array($data['_raw'][$currentLang])) { return array_merge($data, $data['_raw'][$currentLang]); } // Format dates with translated month names if ($dirOrType === 'date_format') { return formatDate($data, $currentLang); } // Filter content files by language variant if (is_array($data) && !empty($data) && isset($data[0]['path'])) { error_log("filterFilesByLanguage called with " . count($data) . " files, current lang: $currentLang"); $filtered = filterFilesByLanguage($data, $dirOrType, $ctx); error_log("Filtered to " . count($filtered) . " files"); return $filtered; } return $data; }); // Add language variables to templates Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) { $currentLang = $ctx->get('currentLang', 'en'); $defaultLang = $ctx->get('defaultLang', 'en'); $availableLangs = $ctx->get('availableLangs', ['en']); $vars['currentLang'] = $currentLang; $vars['defaultLang'] = $defaultLang; $vars['langPrefix'] = $ctx->get('langPrefix', ''); $vars['translations'] = $ctx->get('translations', []); $vars['availableLangs'] = $availableLangs; $vars['languageUrls'] = buildLanguageUrls($ctx, $currentLang, $defaultLang, $availableLangs); return $vars; }); // --- Helper functions --- function loadTranslations(string $lang): array { $defaultFile = dirname(__DIR__, 2) . "/default/languages/$lang.ini"; $customFile = dirname(__DIR__, 3) . "/custom/languages/$lang.ini"; $translations = file_exists($defaultFile) ? parse_ini_file($defaultFile) ?: [] : []; if (file_exists($customFile)) { $translations = array_merge($translations, parse_ini_file($customFile) ?: []); } return $translations; } function formatDate(string $dateString, string $lang): string { if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $dateString, $m)) { return $dateString; } $translations = loadTranslations($lang); $day = (int)$m[3]; $monthIndex = (int)$m[2] - 1; $year = $m[1]; $month = $m[2]; if (isset($translations['months'])) { $months = array_map('trim', explode(',', $translations['months'])); $month = $months[$monthIndex] ?? $m[2]; } return "$day. $month $year"; } function filterFilesByLanguage(array $files, string $dir, Context $ctx): array { $currentLang = $ctx->get('currentLang', 'en'); $defaultLang = $ctx->get('defaultLang', 'en'); $availableLangs = $ctx->get('availableLangs', ['en']); $filtered = []; $seen = []; foreach ($files as $file) { $parts = explode('.', $file['name']); // Language-specific file (name.lang.ext) if (count($parts) >= 3 && in_array($parts[count($parts) - 2], $availableLangs)) { $fileLang = $parts[count($parts) - 2]; if ($fileLang === $currentLang) { $filtered[] = $file; $seen[$parts[0]] = true; } continue; } // Default file - include if no language version exists $baseName = $parts[0]; if (!isset($seen[$baseName])) { $hasLangVersion = $currentLang !== $defaultLang && array_reduce(CONTENT_EXTENSIONS, fn($found, $ext) => $found || file_exists("$dir/$baseName.$currentLang.$ext"), false); if (!$hasLangVersion) { $filtered[] = $file; $seen[$baseName] = true; } } } return $filtered; } function buildLanguageUrls(Context $ctx, string $currentLang, string $defaultLang, array $availableLangs): array { // Frontpage URLs if (empty($ctx->requestPath)) { return array_reduce($availableLangs, fn($urls, $lang) => $urls + [$lang => $lang === $defaultLang ? '/' : "/$lang/"], []); } // Resolve current path to actual folder names $folders = resolvePath($ctx->requestPath, $ctx->contentDir); if (!$folders) { // Fallback: simple language prefix return buildSimpleUrls($ctx, $defaultLang, $availableLangs); } // Build URLs with language-specific slugs $urls = []; foreach ($availableLangs as $lang) { $segments = []; $dir = $ctx->contentDir; foreach ($folders as $folderName) { $metadataFile = "$dir/$folderName/metadata.ini"; $slug = $folderName; if (file_exists($metadataFile)) { $metadata = parse_ini_file($metadataFile, true) ?: []; // Check for language-specific slug if (isset($metadata[$lang]['slug'])) { $slug = $metadata[$lang]['slug']; } elseif (isset($metadata['slug'])) { $slug = $metadata['slug']; } } $segments[] = $slug; $dir .= '/' . $folderName; } $path = '/' . implode('/', $segments); if ($ctx->hasTrailingSlash) $path .= '/'; $urls[$lang] = $lang === $defaultLang ? $path : "/$lang" . $path; } return $urls; } function resolvePath(string $path, string $contentDir): ?array { $parts = explode('/', trim($path, '/')); $folders = []; $dir = $contentDir; foreach ($parts as $slug) { $folderName = resolveSlugToFolder($dir, $slug); if (!$folderName) return null; $folders[] = $folderName; $dir .= '/' . $folderName; } return $folders; } function buildSimpleUrls(Context $ctx, string $defaultLang, array $availableLangs): array { $path = '/' . $ctx->requestPath; if ($ctx->hasTrailingSlash) $path .= '/'; return array_reduce($availableLangs, fn($urls, $lang) => $urls + [$lang => $lang === $defaultLang ? $path : "/$lang" . $path], []); }