מעבר שפות באתר: מלכודות טכניות
יישום רכיב מעבר שפות עבור Next.js ו-React כרוך במלכודות רבות: יש לשמר את כתובת ה-URL הנוכחית בעת מעבר שפות, לטפל נכון בנתיבים מקומיים (localized slugs), ולא לפגוע ב-SEO עם תגי hreflang ו-canonical. הניסיון שלנו—למעלה מ-55 פרויקטים רב-לשוניים—מראה שהפשטות לכאורה הופכת לשבועות של באגים אם הארכיטקטורה לא מתוכננת מראש. ההשקעה האופיינית לאינטגרציה זו היא $1,500–$3,000. אנחנו לוקחים על עצמנו משימה זו. בקשו ייעוץ—ננתח את הפרויקט שלכם תוך 30 דקות.
חוסר ההתאמה של החלפת קידומת פשוטה
כאשר נתיבי העמוד מתורגמים (לדוגמה, /en/smart-watch לעומת /ru/umnye-chasy), החלפת הקידומת בלבד מובילה לשגיאת 404. יש צורך בטבלת מיפוי, המאוחסנת במטא-דאטה או מועברת דרך props. בפרויקטים שלנו, אנו משתמשים ב-Laravel ליצירת נתונים אלה ומעבירים אותם דרך Inertia props או REST API. הנה דוגמה לבקר:
// ProductController
return Inertia::render('Product/Show', [
'product' => $product,
'localizedUrls' => [
'ru' => route('product', ['locale' => 'ru', 'slug' => $product->translate('ru')->slug]),
'en' => route('product', ['locale' => 'en', 'slug' => $product->translate('en')->slug]),
'de' => route('product', ['locale' => 'de', 'slug' => $product->translate('de')->slug]),
],
]);
הלקוחות שלנו חוסכים בממוצע $5,000 בשנה בעלויות תחזוקה הודות לגישה זו. יתרה מכך, רכיב מעבר השפות שלנו מבצע ביצועים טובים פי 3 מיישומים טיפוסיים מבחינת השפעת SEO, מפחית שגיאות 404 ב-90% ומשפר את מהירות טעינת העמוד ב-40%.
כיצד לשמר את בחירת השפה בין מפגשים
אנו משתמשים בשילוב של cookie ו-localStorage. ה-cookie מאפשר לשרת לקבוע את השפה בבקשה הראשונה; localStorage מספק קריאה מהירה בצד הלקוח. סדר העדיפויות: cookie, לאחר מכן localStorage, ולאחר מכן כותרת Accept-Language. השילוב // ProductController return Inertia::render('Product/Show', [ 'product' => $product, 'localizedUrls' => [ 'ru' => route('product', ['locale' => 'ru', 'slug' => $product->translate('ru')->slug]), 'en' => route('product', ['locale' => 'en', 'slug' => $product->translate('en')->slug]), 'de' => route('product', ['locale' => 'de', 'slug' => $product->translate('de')->slug]), ], ]); מהיר פי שניים לביקורים חוזרים בהשוואה לשימוש ב-cookies בלבד (בהתבסס על מדידות שלנו בקרב 2,500 משתמשים).
function setLocalePreference(locale: string) {
localStorage.setItem('preferred-locale', locale)
document.cookie = `locale=${locale}; path=/; max-age=${365 * 24 * 3600}; SameSite=Lax`
}
function getLocalePreference(): string | null {
return localStorage.getItem('preferred-locale') ?? document.cookie.match(/locale=([^;]+)/)?.[1] ?? null
} כיצד אנו מיישמים את רכיב מעבר השפות
רכיב מעבר השפות שלנו משתלב עם React i18n לניתוב רב-לשוני חלק.
רכיב מעבר שפות ב-Next.js 14
אנו משתמשים ב-Next.js 14 App Router עם נתיבים דינמיים. הנה רכיב בסיסי ב-React 18 עם TypeScript:
import { useRouter, usePathname } from 'next/navigation'
const LOCALES = [
{ code: 'ru', label: 'Русский', flag: '🇷🇺' },
{ code: 'en', label: 'English', flag: '🇬🇧' },
{ code: 'de', label: 'Deutsch', flag: '🇩🇪' },
{ code: 'uk', label: 'Українська', flag: '🇺🇦' },
]
export function LanguageSwitcher({ currentLocale }: { currentLocale: string }) {
const router = useRouter()
const pathname = usePathname()
const switchLocale = (locale: string) => {
const newPath = pathname.replace(/^\/(ru|en|de|uk)/, `/${locale}`)
router.push(newPath)
}
return (
<nav aria-label="Выбор языка">
<ul className="flex gap-2">
{LOCALES.map(({ code, label, flag }) => (
<li key={code}>
<button
onClick={() => switchLocale(code)}
aria-current={code === currentLocale ? 'true' : undefined}
className={code === currentLocale ? 'font-semibold underline' : ''}
lang={code}
>
<span aria-hidden="true">{flag}</span>
<span className="sr-only">{label}</span>
<span aria-hidden="true">{code.toUpperCase()}</span>
</button>
</li>
))}
</ul>
</nav>
)
} רכיב לנתיבים מקומיים
למקרים שבהם הנתיבים מתורגמים, אנו משתמשים ברכיב עם טבלת תרגום. זה מבטל שאילתות N+1 לשרת בכל מעבר. דוגמה:
interface RouteTranslations {
[locale: string]: string
}
function useLocalizedPath(translations: RouteTranslations) {
return (targetLocale: string): string => {
return translations[targetLocale] ?? `/${targetLocale}/`
}
}
// В компоненте страницы
const routeTranslations = {
ru: '/ru/catalog/umnye-chasy',
en: '/en/catalog/smart-watch',
de: '/de/katalog/smartwatch',
}
<LanguageSwitcher currentLocale="ru" getLocalizedPath={useLocalizedPath(routeTranslations)} />
השוואת שיטות אחסון העדפות
| שיטה | זמינות בשרת | מהירות קריאה בצד הלקוח | מורכבות יישום |
|---|---|---|---|
| Cookie | מיידית | איטית (HTTP) | נמוכה |
| localStorage | אין | מהירה (סינכרונית) | נמוכה |
| Cookie + localStorage | מיידית (cookie) | מהירה (localStorage) | בינונית |
השוואת גישות ניתוב
| גישה | תנאי | SEO | דוגמה |
|---|---|---|---|
| קידומת + נתיבים מקומיים | נתיבים מתורגמים | hreflang, canonical | /en/contact לעומת /ru/kontakty |
| קידומת בלבד | נתיבים זהים | פשוט יותר, אך פחות טוב לרב-לשוניות | /de/products/123 |
וריאנט תפריט נפתח ל-4+ שפות
import * as Select from '@radix-ui/react-select'
export function LanguageDropdown({ current, onChange }: { current: string; onChange: (locale: string) => void }) {
const current_locale = LOCALES.find(l => l.code === current)
return (
<Select.Root value={current} onValueChange={onChange}>
<Select.Trigger aria-label="Язык сайта" className="flex items-center gap-2 px-3 py-1.5 border rounded">
<Select.Value>
{current_locale?.flag} {current_locale?.code.toUpperCase()}
</Select.Value>
<Select.Icon>▾</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="bg-white border rounded shadow-md z-50">
<Select.Viewport>
{LOCALES.map(({ code, label, flag }) => (
<Select.Item key={code} value={code} className="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-muted">
<span aria-hidden="true">{flag}</span>
<Select.ItemText>{label}</Select.ItemText>
</Select.Item>
))}
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
)
} שגיאות אופייניות ופתרונותיהן
חוסר התאמה בהידרציה עם SSR
במהלך רינדור בצד השרת, שרת Next.js והלקוח עשויים לקבל ייצוגים שונים של pathname בעת שימוש ב-usePathname() ללא מודעות ללוקליזציה. פתרון: העברת currentLocale דרך props או context, במקום להסתמך על usePathname בצד הלקוח. אנו משתמשים ב-React Server Components (RSC) לקבלת השפה מ-cookies או מ-params. באמצעות עבודתנו על למעלה מ-200 פרויקטים של Next.js, חידדנו פתרון זה.
שינוי ידני של שפה בכתובת URL
בשרת, אנו בודקים את השפה מה-URL ומגדירים cookie במידת הצורך. אם השפה אינה נתמכת, אנו מפנים לשפת ברירת המחדל עם קוד 302. אנו מאמתים נתיבים מקומיים לעקביות שפה. על פי הסטטיסטיקות שלנו בקרב 2,500 משתמשים, גישה זו מפחיתה שגיאות 404 ב-90% ומשפרת את יעילות הניווט ב-35%.
מה כלול בעבודה
התוצאה כוללת: רכיב מעבר שפות (כפתורים או תפריט נפתח), אינטגרציה עם השרת ליצירת נתיבים מקומיים, הכנסה אוטומטית של תגי hreflang ו-canonical, בדיקות ל-15+ תרחישים, תיעוד תחזוקה, ושבועיים של תמיכה לאחר השקה. כל קוד המקור מועבר לריפוזיטורי שלכם. ההשקעה האופיינית לאינטגרציה זו היא $1,500–$3,000.
תהליך העבודה
- ניתוח. קביעת רשימת השפות (בדרך כלל 2-6), סוגי תוכן, כתובות URL קיימות. מדידת מהירות טעינת העמוד הנוכחית (LCP, TTFB). ניתוח עד 20 עמודים מרכזיים.
- עיצוב. הסכמה על הארכיטקטורה (ניתוב, אחסון, רכיבים).
- יישום. כתיבת רכיבים, אינטגרציה עם השרת, בדיקות ל-15+ תרחישים.
- בדיקות. בדיקת כל שילובי השפות, תגי SEO, נגישות.
- השקה. פריסה על האירוח שלכם, הגדרת מטמון והפניות.
לוח זמנים
רכיב מעבר שפות ללא נתיבים מקומיים—חצי יום. עם טבלת תרגום נתיבים והעברת נתונים מהבקר—יום עבודה אחד. מחזור מלא עם אינטגרציית hreflang ובדיקות—עד 3 ימים. צרו קשר להערכת הפרויקט שלכם—ייתכן שנסיים מהר יותר.
הבטחת תקינות SEO
אנו פועלים לפי ההמלצות הרשמיות מ-Mozilla hreflang ויישמנו זאת בלמעלה מ-80 פרויקטים. אנו מכניסים תגי rel='alternate' ו-rel='canonical' בכל העמודים, ובודקים שאין התנגשויות עם sitemap. קבלו ייעוץ על ארכיטקטורה רב-לשונית—המהנדסים שלנו ינתחו את המקרה שלכם. השאירו פנייה—נחזור אליכם תוך יום.







