This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { routing } from "@/i18n/routing";
|
||||
|
||||
export type AppLocale = (typeof routing.locales)[number];
|
||||
|
||||
export function resolveLocale(locale: string): AppLocale {
|
||||
if (locale === "en" || locale === "ar") {
|
||||
return locale;
|
||||
}
|
||||
|
||||
return routing.defaultLocale;
|
||||
}
|
||||
|
||||
export function getDirection(locale: string): "ltr" | "rtl" {
|
||||
return resolveLocale(locale) === "ar" ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
export function stripLocalePrefix(pathname: string): string {
|
||||
for (const locale of routing.locales) {
|
||||
if (pathname === `/${locale}`) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
if (pathname.startsWith(`/${locale}/`)) {
|
||||
return pathname.slice(locale.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return pathname || "/";
|
||||
}
|
||||
|
||||
export function getLocalizedPath(locale: string, pathname = "/"): string {
|
||||
const localeKey = resolveLocale(locale);
|
||||
const normalizedPath = pathname === "" ? "/" : pathname;
|
||||
const strippedPath = stripLocalePrefix(normalizedPath);
|
||||
|
||||
if (localeKey === routing.defaultLocale) {
|
||||
return strippedPath;
|
||||
}
|
||||
|
||||
return strippedPath === "/" ? `/${localeKey}` : `/${localeKey}${strippedPath}`;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { routing } from "@/i18n/routing";
|
||||
import { AppLocale, getLocalizedPath, resolveLocale } from "@/lib/locale";
|
||||
|
||||
function getSiteUrl(): URL {
|
||||
return new URL(process.env.NEXT_PUBLIC_SITE_URL ?? "https://mohfarawati.de");
|
||||
}
|
||||
|
||||
function toAbsoluteUrl(pathname: string): string {
|
||||
return new URL(pathname, getSiteUrl()).toString();
|
||||
}
|
||||
|
||||
export function buildLocaleAlternates(pathname: string) {
|
||||
const languages = Object.fromEntries(
|
||||
routing.locales.map((locale) => [locale, toAbsoluteUrl(getLocalizedPath(locale, pathname))]),
|
||||
) as Record<AppLocale, string>;
|
||||
|
||||
return {
|
||||
canonical: toAbsoluteUrl(getLocalizedPath(routing.defaultLocale, pathname)),
|
||||
languages: {
|
||||
...languages,
|
||||
"x-default": toAbsoluteUrl(getLocalizedPath(routing.defaultLocale, pathname)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type LocalizedMetadataInput = {
|
||||
locale: string;
|
||||
pathname: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function buildLocalizedMetadata({
|
||||
locale,
|
||||
pathname,
|
||||
title,
|
||||
description,
|
||||
}: LocalizedMetadataInput): Metadata {
|
||||
const localeKey = resolveLocale(locale);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: buildLocaleAlternates(pathname),
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: toAbsoluteUrl(getLocalizedPath(localeKey, pathname)),
|
||||
siteName: "moh-sass",
|
||||
locale: localeKey,
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { LayoutDashboard, ShieldAlert, SwatchBook, type LucideIcon } from "lucide-react";
|
||||
|
||||
type RootNavigationCopy = {
|
||||
overview: string;
|
||||
maintenance: string;
|
||||
uiKit: string;
|
||||
};
|
||||
|
||||
export type RootNavItem = {
|
||||
label: string;
|
||||
href: string;
|
||||
icon: LucideIcon;
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
export function getRootNavigation(
|
||||
copy: RootNavigationCopy,
|
||||
active: "overview" | "maintenance" | "ui-kit",
|
||||
): RootNavItem[] {
|
||||
return [
|
||||
{
|
||||
label: copy.overview,
|
||||
href: "/root",
|
||||
icon: LayoutDashboard,
|
||||
active: active === "overview",
|
||||
},
|
||||
{
|
||||
label: copy.maintenance,
|
||||
href: "/root/maintenance",
|
||||
icon: ShieldAlert,
|
||||
active: active === "maintenance",
|
||||
},
|
||||
{
|
||||
label: copy.uiKit,
|
||||
href: "/root/ui-kit",
|
||||
icon: SwatchBook,
|
||||
active: active === "ui-kit",
|
||||
},
|
||||
];
|
||||
}
|
||||
+25
-5
@@ -1,4 +1,4 @@
|
||||
export type AppLocale = "de" | "en";
|
||||
import type { AppLocale } from "@/lib/locale";
|
||||
|
||||
type LocalizedText = Record<AppLocale, string>;
|
||||
|
||||
@@ -24,14 +24,17 @@ export const portfolioItems: PortfolioItem[] = [
|
||||
title: {
|
||||
de: "Brand Redesign",
|
||||
en: "Brand Redesign",
|
||||
ar: "إعادة تصميم الهوية",
|
||||
},
|
||||
summary: {
|
||||
de: "Modernes Redesign fuer eine digitale Marke mit klarer Struktur.",
|
||||
en: "Modern redesign for a digital brand with a clear system.",
|
||||
ar: "إعادة تصميم حديثة لعلامة رقمية مع بنية واضحة.",
|
||||
},
|
||||
category: {
|
||||
de: "Branding",
|
||||
en: "Branding",
|
||||
ar: "الهوية",
|
||||
},
|
||||
year: "2025",
|
||||
},
|
||||
@@ -40,14 +43,17 @@ export const portfolioItems: PortfolioItem[] = [
|
||||
title: {
|
||||
de: "Commerce Relaunch",
|
||||
en: "Commerce Relaunch",
|
||||
ar: "إعادة إطلاق المتجر",
|
||||
},
|
||||
summary: {
|
||||
de: "Relaunch eines Shops mit Fokus auf Performance und Conversion.",
|
||||
en: "Store relaunch focused on performance and conversion.",
|
||||
ar: "إعادة إطلاق متجر مع تركيز على الأداء والتحويل.",
|
||||
},
|
||||
category: {
|
||||
de: "E-Commerce",
|
||||
en: "E-Commerce",
|
||||
ar: "التجارة الإلكترونية",
|
||||
},
|
||||
year: "2024",
|
||||
},
|
||||
@@ -56,14 +62,17 @@ export const portfolioItems: PortfolioItem[] = [
|
||||
title: {
|
||||
de: "SaaS Dashboard",
|
||||
en: "SaaS Dashboard",
|
||||
ar: "لوحة تحكم SaaS",
|
||||
},
|
||||
summary: {
|
||||
de: "Admin Dashboard fuer Teams mit klaren KPIs und Reports.",
|
||||
en: "Admin dashboard for teams with clear KPIs and reports.",
|
||||
ar: "لوحة تحكم إدارية للفرق مع مؤشرات وتقارير واضحة.",
|
||||
},
|
||||
category: {
|
||||
de: "Web App",
|
||||
en: "Web App",
|
||||
ar: "تطبيق ويب",
|
||||
},
|
||||
year: "2024",
|
||||
},
|
||||
@@ -72,14 +81,17 @@ export const portfolioItems: PortfolioItem[] = [
|
||||
title: {
|
||||
de: "Campaign Site",
|
||||
en: "Campaign Site",
|
||||
ar: "موقع حملة",
|
||||
},
|
||||
summary: {
|
||||
de: "Landing Seite fuer Produktkampagnen mit schneller Iteration.",
|
||||
en: "Landing experience for product campaigns and quick iteration.",
|
||||
ar: "صفحة هبوط لحملات المنتجات مع تنفيذ سريع.",
|
||||
},
|
||||
category: {
|
||||
de: "Marketing",
|
||||
en: "Marketing",
|
||||
ar: "التسويق",
|
||||
},
|
||||
year: "2023",
|
||||
},
|
||||
@@ -91,18 +103,22 @@ export const productItems: ProductItem[] = [
|
||||
name: {
|
||||
de: "Starter Kit",
|
||||
en: "Starter Kit",
|
||||
ar: "Starter Kit",
|
||||
},
|
||||
summary: {
|
||||
de: "Basis Paket fuer den schnellen Start von neuen Projekten.",
|
||||
en: "Base package for launching new projects quickly.",
|
||||
ar: "باقة أساسية لبدء المشاريع الجديدة بسرعة.",
|
||||
},
|
||||
segment: {
|
||||
de: "Small Teams",
|
||||
en: "Small Teams",
|
||||
ar: "الفرق الصغيرة",
|
||||
},
|
||||
price: {
|
||||
de: "ab 990 EUR",
|
||||
en: "from 990 EUR",
|
||||
ar: "ابتداءً من 990 EUR",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -110,18 +126,22 @@ export const productItems: ProductItem[] = [
|
||||
name: {
|
||||
de: "Growth Kit",
|
||||
en: "Growth Kit",
|
||||
ar: "Growth Kit",
|
||||
},
|
||||
summary: {
|
||||
de: "Skalierbares Paket fuer wachsende Produkte und Prozesse.",
|
||||
en: "Scalable package for growing products and processes.",
|
||||
ar: "باقة قابلة للتوسع للمنتجات والعمليات المتنامية.",
|
||||
},
|
||||
segment: {
|
||||
de: "Scaleups",
|
||||
en: "Scaleups",
|
||||
ar: "الشركات المتوسعة",
|
||||
},
|
||||
price: {
|
||||
de: "ab 2490 EUR",
|
||||
en: "from 2490 EUR",
|
||||
ar: "ابتداءً من 2490 EUR",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -129,26 +149,26 @@ export const productItems: ProductItem[] = [
|
||||
name: {
|
||||
de: "Enterprise Kit",
|
||||
en: "Enterprise Kit",
|
||||
ar: "Enterprise Kit",
|
||||
},
|
||||
summary: {
|
||||
de: "Massgeschneiderte Loesung fuer grosse Teams und komplexe Systeme.",
|
||||
en: "Tailored solution for large teams and complex systems.",
|
||||
ar: "حل مخصص للفرق الكبيرة والأنظمة المعقدة.",
|
||||
},
|
||||
segment: {
|
||||
de: "Enterprise",
|
||||
en: "Enterprise",
|
||||
ar: "المؤسسات",
|
||||
},
|
||||
price: {
|
||||
de: "auf Anfrage",
|
||||
en: "on request",
|
||||
ar: "عند الطلب",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function resolveLocale(locale: string): AppLocale {
|
||||
return locale === "en" ? "en" : "de";
|
||||
}
|
||||
|
||||
export function pickText(text: LocalizedText, locale: AppLocale): string {
|
||||
return text[locale];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user