= 3) { // Pattern: name.lang.ext $fileLang = $parts[count($parts) - 2]; if (in_array($fileLang, ['no', 'en'])) { // Only include if it matches current language if ($fileLang === $lang) { $contentFiles[] = [ 'path' => $filePath, 'name' => $file, 'sort_key' => $parts[0] // Use base name for sorting ]; } continue; } } // Default files (no language suffix) - include if current lang is default // or if no language-specific version exists $baseName = $parts[0]; $hasLangVersion = false; if ($lang !== $defaultLang) { // Check if language-specific version exists foreach ($extensions as $checkExt) { if (file_exists("$dir/$baseName.$lang.$checkExt")) { $hasLangVersion = true; break; } } } if (!$hasLangVersion) { $contentFiles[] = [ 'path' => $filePath, 'name' => $file, 'sort_key' => $baseName ]; } } // Sort by filename (alphanumerical) usort($contentFiles, fn($a, $b) => strnatcmp($a['sort_key'], $b['sort_key'])); return array_column($contentFiles, 'path'); } function resolveTranslatedPath(string $requestPath, string $contentDir, string $lang, string $defaultLang): string { // If default language, no translation needed if ($lang === $defaultLang) { return $requestPath; } $parts = explode('/', trim($requestPath, '/')); $resolvedParts = []; $currentPath = $contentDir; foreach ($parts as $segment) { if (empty($segment)) continue; // Check all subdirectories for slug matches $found = false; if (is_dir($currentPath)) { $subdirs = array_filter( scandir($currentPath) ?: [], fn($item) => !in_array($item, ['.', '..']) && is_dir("$currentPath/$item") ); foreach ($subdirs as $dir) { $metadata = loadMetadata("$currentPath/$dir", $lang, $defaultLang); if ($metadata && isset($metadata['slug']) && $metadata['slug'] === $segment) { $resolvedParts[] = $dir; $currentPath .= "/$dir"; $found = true; break; } } } // If no slug match, use segment as-is if (!$found) { $resolvedParts[] = $segment; $currentPath .= "/$segment"; } } return implode('/', $resolvedParts); } function parseRequestPath(string $requestPath, string $contentDir, bool $hasTrailingSlash, string $lang, string $defaultLang): array { // Resolve translated slugs to actual directory names $resolvedPath = resolveTranslatedPath($requestPath, $contentDir, $lang, $defaultLang); $contentPath = rtrim($contentDir, '/') . '/' . ltrim($resolvedPath, '/'); if (is_file($contentPath)) { return ['type' => 'file', 'path' => realpath($contentPath)]; } if (is_dir($contentPath)) { // Check if directory has subdirectories $hasSubdirs = !empty(array_filter( scandir($contentPath) ?: [], fn($item) => !in_array($item, ['.', '..']) && is_dir("$contentPath/$item") )); // If directory has subdirectories, it's an article-type folder (list view) if ($hasSubdirs) { return ['type' => 'directory', 'path' => realpath($contentPath)]; } // No subdirectories - it's a page-type folder // Find all content files in this directory $contentFiles = findAllContentFiles($contentPath, $lang, $defaultLang); if (!empty($contentFiles)) { return ['type' => 'page', 'path' => realpath($contentPath), 'files' => $contentFiles, 'needsSlash' => !$hasTrailingSlash]; } // No content files found return ['type' => 'directory', 'path' => realpath($contentPath)]; } return ['type' => 'not_found', 'path' => $contentPath]; } function loadMetadata(string $dirPath, string $lang, string $defaultLang): ?array { $metadataFile = "$dirPath/metadata.ini"; if (!file_exists($metadataFile)) return null; $metadata = parse_ini_file($metadataFile, true); if (!$metadata) return null; // Extract base metadata (non-section values) $baseMetadata = array_filter($metadata, fn($key) => !is_array($metadata[$key]), ARRAY_FILTER_USE_KEY); // If current language is not default, merge language-specific overrides if ($lang !== $defaultLang && isset($metadata[$lang]) && is_array($metadata[$lang])) { $baseMetadata = array_merge($baseMetadata, $metadata[$lang]); } return $baseMetadata ?: null; } function loadTranslations(string $lang): array { $translationFile = dirname(__DIR__) . "/custom/languages/$lang.ini"; if (file_exists($translationFile)) { return parse_ini_file($translationFile) ?: []; } return []; } function buildNavigation(string $contentDir, string $currentLang, string $defaultLang): array { $navItems = []; // Scan top-level directories in content $items = array_filter( scandir($contentDir) ?: [], fn($item) => !in_array($item, ['.', '..']) && is_dir("$contentDir/$item") ); foreach ($items as $item) { $itemPath = "$contentDir/$item"; $metadata = loadMetadata($itemPath, $currentLang, $defaultLang); // Check if this item should be in menu if (!$metadata || empty($metadata['menu'])) { continue; } // Check if content exists for current language if ($currentLang !== $defaultLang) { $contentFiles = findAllContentFiles($itemPath, $currentLang, $defaultLang); // If no content files, check if metadata has title for this language $hasContent = !empty($contentFiles) || ($metadata && isset($metadata['title'])); if (!$hasContent) continue; } // Extract title and build URL $title = $metadata['title'] ?? extractTitle($itemPath, $currentLang, $defaultLang) ?? ucfirst($item); $langPrefix = $currentLang !== $defaultLang ? "/$currentLang" : ''; // Use translated slug if available $urlSlug = ($currentLang !== $defaultLang && $metadata && isset($metadata['slug'])) ? $metadata['slug'] : $item; $navItems[] = [ 'title' => $title, 'url' => $langPrefix . '/' . urlencode($urlSlug) . '/', 'order' => (int)($metadata['menu_order'] ?? 999) ]; } // Sort by menu_order usort($navItems, fn($a, $b) => $a['order'] <=> $b['order']); return $navItems; }