Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
70b0ad6
feat(VCST-3986): multilingual seo urls
ivan-kalachikov Oct 1, 2025
655ee64
feat: minor refactor
ivan-kalachikov Oct 1, 2025
25ad058
feat: minor refactor
ivan-kalachikov Oct 1, 2025
db795ad
feat: minor refactor
ivan-kalachikov Oct 1, 2025
4947ea8
feat: minor refactor
ivan-kalachikov Oct 1, 2025
990f41c
fix: typo
ivan-kalachikov Oct 1, 2025
a50b9d3
feat: add brazilian portugues
ivan-kalachikov Oct 1, 2025
a229916
feat: minor refactor
ivan-kalachikov Oct 1, 2025
98909d0
feat: implement url localization for brand, product, category and page
ivan-kalachikov Oct 1, 2025
294dcda
feat: minor refactor
ivan-kalachikov Oct 1, 2025
e79088c
feat: add unit tests
ivan-kalachikov Oct 1, 2025
e11bcbd
feat: add test cases
ivan-kalachikov Oct 1, 2025
e05f564
feat: add test cases
ivan-kalachikov Oct 1, 2025
6b762ad
fix: sonar issues
ivan-kalachikov Oct 1, 2025
3368315
fix: sonar issues
ivan-kalachikov Oct 1, 2025
569a87b
fix: sonar issues
ivan-kalachikov Oct 1, 2025
410dd25
fix: path issue and remove unused
ivan-kalachikov Oct 2, 2025
a56e1a3
fix: remove console.log
ivan-kalachikov Oct 2, 2025
8aca6ed
feat: store permalink and culture name when switching languages, use …
ivan-kalachikov Oct 2, 2025
7cd34f3
fix: unit tests
ivan-kalachikov Oct 2, 2025
269d3d7
feat: update readme
ivan-kalachikov Oct 2, 2025
af4d3bc
feat: update readme
ivan-kalachikov Oct 2, 2025
a7e9d4f
fix: add decoding permalink (for non latin)
ivan-kalachikov Oct 2, 2025
5754ed7
fix: add decoding permalink (for non latin)
ivan-kalachikov Oct 2, 2025
eb20676
Merge branch 'dev' into feat/VCST-3986-seo-multilingual-urls
ivan-kalachikov Oct 2, 2025
63c65db
fix: delete doubled call
ivan-kalachikov Oct 3, 2025
d87f2cb
Merge branch 'dev' into feat/VCST-3986-seo-multilingual-urls
Lenajava1 Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,17 @@ If command "dot" is missing - install graphviz on your OS
The generated graph will also be saved in the `artifacts` folder.

## Localization
### Language Flow
- Locale selection checks the URL first - using the supported languages list to tell real locale segments from ordinary path parts (full culture like `fr-FR` or an unambiguous short alias such as `fr`), then your “pinned” locale - your last choice saved in localStorage, then your account’s preferred culture, and finally the store default.

- When a locale is chosen, useLanguages.initLocale lazy-loads its translation file (`xx-YY.json`; if missing it falls back to `xx.json`, then to `en.json`), wires it into Vue I18n and Yup, updates the `<html lang>` tag, and rewrites the URL so default or mismatched locale segments vanish.

- Switching via the header selector stores the new culture, captures the exact slug and culture you were on at that moment (previousCultureSlug in session storage), strips any stale locale prefix from the URL, broadcasts a data refresh, and reloads so the whole app restarts in the new language.

- After that reload, useSlugInfo notices previousCultureSlug; while you stay on the same path it asks the backend for slug data using the recorded culture, so `/hello` is resolved with `en-US` instead of the new `fr-FR`, preventing empty responses before the localized slug is known.

- When product, category, brand, or CMS data brings back a localized permalink (updateLocalizedUrl in those page modules), it uses history.pushState to refresh the browser address with the localized path - keeping locale prefix, query string, and hash - so users see the correct URL without triggering router navigation or extra data fetching.

### Check for missing locale keys
```
yarn check-locales --source en.json -- path/to/locales_folder path/to/**/locales
Expand Down
35 changes: 15 additions & 20 deletions client-app/app-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
extensionPointsPlugin,
permissionsPlugin,
} from "@/core/plugins";
import { extractHostname, getBaseUrl, Logger } from "@/core/utilities";
import { extractHostname, Logger } from "@/core/utilities";
import { createI18n } from "@/i18n";
import { init as initModuleBackInStock } from "@/modules/back-in-stock";
import { init as initCustomerReviews } from "@/modules/customer-reviews";
Expand Down Expand Up @@ -66,17 +66,16 @@ export default async () => {

app.use(authPlugin);

const { fetchUser, user, twoLetterContactLocale, isAuthenticated } = useUser();
const { fetchUser, user, isAuthenticated } = useUser();
const { themeContext, addPresetToThemeContext, setThemeContext } = useThemeContext();
const {
detectLocale,
currentLanguage,
supportedLocales,
currentMaybeShortLocale,
defaultStoreCulture,
initLocale,
fetchLocaleMessages,
getLocaleFromUrl,
pinedLocale,
mergeLocales,
mergeLocalesMessages,
resolveLocale,
} = useLanguages();
const { currentCurrency } = useCurrency();
const { init: initializeHotjar } = useHotjar();
Expand Down Expand Up @@ -104,22 +103,18 @@ export default async () => {

setThemeContext(store);

// priority rule: pinedLocale > contactLocale > urlLocale > storeLocale
const twoLetterAppLocale = detectLocale([
pinedLocale.value,
twoLetterContactLocale.value,
getLocaleFromUrl(),
themeContext.value.defaultLanguage.twoLetterLanguageName,
]);

/**
* Creating plugin instances
*/
const head = createHead();
const i18n = createI18n(twoLetterAppLocale, currentCurrency.value.code, fallback);
const router = createRouter({ base: getBaseUrl(supportedLocales.value) });

await initLocale(i18n, twoLetterAppLocale);
const currentCultureName = resolveLocale();
const isDefaultLocaleInUse = defaultStoreCulture.value === currentCultureName;

const i18n = createI18n(currentCultureName, currentCurrency.value.code, fallback);
await initLocale(i18n, currentCultureName);

const router = createRouter({ base: isDefaultLocaleInUse ? "" : currentMaybeShortLocale.value });

/**
* Setting global variables
Expand Down Expand Up @@ -167,9 +162,9 @@ export default async () => {
app.use(configPlugin, themeContext.value);

const UIKitMessages = await getUIKitLocales(FALLBACK_LOCALE, currentLanguage.value?.twoLetterLanguageName);
mergeLocales(i18n, currentLanguage.value?.twoLetterLanguageName, UIKitMessages.messages);
mergeLocalesMessages(i18n, currentLanguage.value?.twoLetterLanguageName, UIKitMessages.messages);
if (currentLanguage.value?.twoLetterLanguageName !== FALLBACK_LOCALE) {
mergeLocales(i18n, FALLBACK_LOCALE, UIKitMessages.fallbackMessages);
mergeLocalesMessages(i18n, FALLBACK_LOCALE, UIKitMessages.fallbackMessages);
}
app.use(uiKit);

Expand Down
Loading
Loading