slug i18n

This commit is contained in:
2026-01-20 23:43:01 +01:00
parent f62485a67d
commit abf283c9ab
9 changed files with 267 additions and 42 deletions

View File

@@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { mapSlugToFileSlug } from './slugs';
export interface PostFrontmatter {
title: string;
@@ -18,8 +19,10 @@ export interface PostMdx {
}
export async function getPostBySlug(slug: string, locale: string): Promise<PostMdx | null> {
// Map translated slug to file slug
const fileSlug = await mapSlugToFileSlug(slug, locale);
const postsDir = path.join(process.cwd(), 'data', 'blog', locale);
const filePath = path.join(postsDir, `${slug}.mdx`);
const filePath = path.join(postsDir, `${fileSlug}.mdx`);
if (!fs.existsSync(filePath)) {
return null;
@@ -29,7 +32,7 @@ export async function getPostBySlug(slug: string, locale: string): Promise<PostM
const { data, content } = matter(fileContent);
return {
slug,
slug: fileSlug,
frontmatter: data as PostFrontmatter,
content,
};

View File

@@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { mapSlugToFileSlug } from './slugs';
export interface ProductFrontmatter {
title: string;
@@ -18,30 +19,32 @@ export interface ProductMdx {
}
export async function getProductBySlug(slug: string, locale: string): Promise<ProductMdx | null> {
// Map translated slug to file slug
const fileSlug = await mapSlugToFileSlug(slug, locale);
const productsDir = path.join(process.cwd(), 'data', 'products', locale);
// Try exact slug first
let filePath = path.join(productsDir, `${slug}.mdx`);
let filePath = path.join(productsDir, `${fileSlug}.mdx`);
if (!fs.existsSync(filePath)) {
// Try with -2 suffix (common in the dumped files)
filePath = path.join(productsDir, `${slug}-2.mdx`);
filePath = path.join(productsDir, `${fileSlug}-2.mdx`);
}
if (!fs.existsSync(filePath)) {
// Fallback to English if locale is not 'en'
if (locale !== 'en') {
const enProductsDir = path.join(process.cwd(), 'data', 'products', 'en');
let enFilePath = path.join(enProductsDir, `${slug}.mdx`);
let enFilePath = path.join(enProductsDir, `${fileSlug}.mdx`);
if (!fs.existsSync(enFilePath)) {
enFilePath = path.join(enProductsDir, `${slug}-2.mdx`);
enFilePath = path.join(enProductsDir, `${fileSlug}-2.mdx`);
}
if (fs.existsSync(enFilePath)) {
const fileContent = fs.readFileSync(enFilePath, 'utf8');
const { data, content } = matter(fileContent);
return {
slug,
slug: fileSlug,
frontmatter: {
...data,
isFallback: true
@@ -57,7 +60,7 @@ export async function getProductBySlug(slug: string, locale: string): Promise<Pr
const { data, content } = matter(fileContent);
return {
slug,
slug: fileSlug,
frontmatter: data as ProductFrontmatter,
content,
};

View File

@@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { mapSlugToFileSlug } from './slugs';
export interface PageFrontmatter {
title: string;
@@ -16,8 +17,10 @@ export interface PageMdx {
}
export async function getPageBySlug(slug: string, locale: string): Promise<PageMdx | null> {
// Map translated slug to file slug
const fileSlug = await mapSlugToFileSlug(slug, locale);
const pagesDir = path.join(process.cwd(), 'data', 'pages', locale);
const filePath = path.join(pagesDir, `${slug}.mdx`);
const filePath = path.join(pagesDir, `${fileSlug}.mdx`);
if (!fs.existsSync(filePath)) {
return null;
@@ -27,7 +30,7 @@ export async function getPageBySlug(slug: string, locale: string): Promise<PageM
const { data, content } = matter(fileContent);
return {
slug,
slug: fileSlug,
frontmatter: data as PageFrontmatter,
content,
};
@@ -41,7 +44,17 @@ export async function getAllPages(locale: string): Promise<PageMdx[]> {
const pages = await Promise.all(
files
.filter(file => file.endsWith('.mdx'))
.map(file => getPageBySlug(file.replace(/\.mdx$/, ''), locale))
.map(file => {
const fileSlug = file.replace(/\.mdx$/, '');
const filePath = path.join(pagesDir, file);
const fileContent = { content: fs.readFileSync(filePath, 'utf8') };
const { data, content } = matter(fileContent.content);
return {
slug: fileSlug,
frontmatter: data as PageFrontmatter,
content,
};
})
);
return pages.filter((p): p is PageMdx => p !== null);

96
lib/slugs.ts Normal file
View File

@@ -0,0 +1,96 @@
import { getTranslations } from 'next-intl/server';
/**
* Maps a translated slug to original file slug
* @param translatedSlug - The slug from URL (translated)
* @param _locale - The current locale (unused, kept for API consistency)
* @returns The original file slug, or input slug if no mapping exists
*/
export async function mapSlugToFileSlug(translatedSlug: string, _locale: string): Promise<string> {
const t = await getTranslations('Slugs');
// Check pages
try {
const pageSlug = t.raw(`pages.${translatedSlug}`);
if (pageSlug && typeof pageSlug === 'string') {
return pageSlug;
}
} catch {
// Key doesn't exist, continue
}
// Check products
try {
const productSlug = t.raw(`products.${translatedSlug}`);
if (productSlug && typeof productSlug === 'string') {
return productSlug;
}
} catch {
// Key doesn't exist, continue
}
// Check categories
try {
const categorySlug = t.raw(`categories.${translatedSlug}`);
if (categorySlug && typeof categorySlug === 'string') {
return categorySlug;
}
} catch {
// Key doesn't exist, continue
}
// Return original slug if no mapping found
return translatedSlug;
}
/**
* Maps an original file slug to translated slug for a locale
* @param fileSlug - The original file slug
* @param _locale - The target locale (unused, kept for API consistency)
* @returns The translated slug, or input slug if no mapping exists
*/
export async function mapFileSlugToTranslated(fileSlug: string, _locale: string): Promise<string> {
const t = await getTranslations('Slugs');
// Find the key that maps to this file slug
const sections = ['pages', 'products', 'categories'];
for (const section of sections) {
try {
const sectionData = t.raw(section);
if (sectionData && typeof sectionData === 'object') {
for (const [translatedSlug, mappedSlug] of Object.entries(sectionData)) {
if (mappedSlug === fileSlug) {
return translatedSlug;
}
}
}
} catch {
// Section doesn't exist, continue
}
}
// Return original slug if no mapping found
return fileSlug;
}
/**
* Gets all translated slugs for a section
* @param section - The section name (pages, products, categories)
* @param _locale - The current locale (unused, kept for API consistency)
* @returns Object mapping translated slugs to file slugs
*/
export async function getSlugMappings(section: 'pages' | 'products' | 'categories', _locale: string): Promise<Record<string, string>> {
const t = await getTranslations('Slugs');
try {
const sectionData = t.raw(section);
if (sectionData && typeof (sectionData as any) === 'object') {
return sectionData as Record<string, string>;
}
} catch {
// Section doesn't exist
}
return {};
}