itube-specs 0.0.840 → 0.0.841
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/layout/footer-language.vue +1 -1
- package/components/layout/language.vue +1 -16
- package/composables/__tests__/use-seo-links.test.ts +22 -0
- package/composables/use-enabled-locales.ts +17 -0
- package/composables/use-seo-links.ts +6 -1
- package/package.json +1 -1
- package/plugins/enabled-locales.server.ts +20 -0
|
@@ -66,7 +66,7 @@ function handler() {
|
|
|
66
66
|
isMenuShow.value = !isMenuShow.value;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const enabledLocales =
|
|
69
|
+
const enabledLocales = useEnabledLocales();
|
|
70
70
|
|
|
71
71
|
const localeName = computed(() => locales.value.find(item => item.code === locale.value)?.name || 'English');
|
|
72
72
|
|
|
@@ -120,8 +120,6 @@ defineProps<{
|
|
|
120
120
|
}>();
|
|
121
121
|
|
|
122
122
|
const countriesItems = useState<ISelectItem[]>('countriesItems', () => []);
|
|
123
|
-
const enabledLanguageCodesState = useState<string[]>('enabledLanguageCodes', () => []);
|
|
124
|
-
|
|
125
123
|
const clientCountryCode = useState<string>('clientCountryCode', () => '');
|
|
126
124
|
|
|
127
125
|
const clientCountryIcon = computed(() => {
|
|
@@ -146,13 +144,6 @@ const filteredCountries = computed(() => {
|
|
|
146
144
|
return countriesItems.value.filter(item => item.title.toLowerCase().includes(query));
|
|
147
145
|
});
|
|
148
146
|
|
|
149
|
-
if (!enabledLanguageCodesState.value.length) {
|
|
150
|
-
const { data: enabledLanguageCodes } = await useFetch<string[]>('/bff/site/languages', { default: () => [] });
|
|
151
|
-
if (enabledLanguageCodes.value?.length) {
|
|
152
|
-
enabledLanguageCodesState.value = enabledLanguageCodes.value;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
147
|
const isCountryLoading = ref(false);
|
|
157
148
|
|
|
158
149
|
// Список стран не приходит в payload (см. plugins/countries.server.ts) — догружаем
|
|
@@ -178,13 +169,7 @@ function onCountryToggle(e: Event) {
|
|
|
178
169
|
if (isCountryOpen.value) loadCountries();
|
|
179
170
|
}
|
|
180
171
|
|
|
181
|
-
const enabledLocales =
|
|
182
|
-
|
|
183
|
-
watch(enabledLanguageCodesState, (codes) => {
|
|
184
|
-
enabledLocales.value = codes?.length
|
|
185
|
-
? locales.value.filter(item => codes.includes(item.code))
|
|
186
|
-
: locales.value;
|
|
187
|
-
}, { immediate: true });
|
|
172
|
+
const enabledLocales = useEnabledLocales();
|
|
188
173
|
|
|
189
174
|
const selectedLocale = ref(locale.value);
|
|
190
175
|
|
|
@@ -3,6 +3,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
3
3
|
import { mockNuxtImport } from '@nuxt/test-utils/runtime';
|
|
4
4
|
import { useSeoLinks } from '../use-seo-links';
|
|
5
5
|
import { useSeoOverridesState } from '../use-seo-overrides';
|
|
6
|
+
import { useEnabledLanguageCodes } from '../use-enabled-locales';
|
|
6
7
|
|
|
7
8
|
let mockRoute = { path: '/videos', query: {} as Record<string, any> };
|
|
8
9
|
|
|
@@ -23,6 +24,7 @@ describe('useSeoLinks', () => {
|
|
|
23
24
|
mockRoute = { path: '/videos', query: {} };
|
|
24
25
|
mockI18n.locale.value = 'en';
|
|
25
26
|
useSeoOverridesState().value = null;
|
|
27
|
+
useEnabledLanguageCodes().value = [];
|
|
26
28
|
});
|
|
27
29
|
|
|
28
30
|
it('canonical URL', () => {
|
|
@@ -63,6 +65,26 @@ describe('useSeoLinks', () => {
|
|
|
63
65
|
expect(hreflangs).toContain('x-default');
|
|
64
66
|
});
|
|
65
67
|
|
|
68
|
+
it('alternate links только для включённых на сайте локалей', () => {
|
|
69
|
+
useEnabledLanguageCodes().value = ['en', 'fr'];
|
|
70
|
+
const { alternateLinks } = useSeoLinks('https://example.com', mockI18n, mockLocalePath);
|
|
71
|
+
const hreflangs = alternateLinks.value.map(l => l.hreflang);
|
|
72
|
+
expect(hreflangs).toContain('en');
|
|
73
|
+
expect(hreflangs).toContain('fr');
|
|
74
|
+
expect(hreflangs).toContain('x-default');
|
|
75
|
+
expect(hreflangs).not.toContain('de');
|
|
76
|
+
expect(hreflangs).not.toContain('ja-JP');
|
|
77
|
+
expect(hreflangs).not.toContain('zh-CN');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('текущая локаль остаётся в alternate, даже если её нет в списке включённых', () => {
|
|
81
|
+
mockRoute = { path: '/ja/videos', query: {} };
|
|
82
|
+
mockI18n.locale.value = 'ja';
|
|
83
|
+
useEnabledLanguageCodes().value = ['en', 'fr'];
|
|
84
|
+
const { alternateLinks } = useSeoLinks('https://example.com', mockI18n, mockLocalePath);
|
|
85
|
+
expect(alternateLinks.value.map(l => l.hreflang)).toContain('ja-JP');
|
|
86
|
+
});
|
|
87
|
+
|
|
66
88
|
it('ja маппится в ja-JP', () => {
|
|
67
89
|
const { alternateLinks } = useSeoLinks('https://example.com', mockI18n, mockLocalePath);
|
|
68
90
|
const jaLink = alternateLinks.value.find(l => l.hreflang === 'ja-JP');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Коды локалей, реально включённых на сайте. Заполняет plugins/enabled-locales.server.ts,
|
|
3
|
+
* значение уезжает в payload. Пустой список = данных нет, потребители используют все локали.
|
|
4
|
+
*/
|
|
5
|
+
export const useEnabledLanguageCodes = () => useState<string[]>('enabledLanguageCodes', () => []);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Локали i18n, отфильтрованные по включённым на сайте — для переключателей языка и hreflang.
|
|
9
|
+
*/
|
|
10
|
+
export const useEnabledLocales = () => {
|
|
11
|
+
const { locales } = useI18n();
|
|
12
|
+
const codes = useEnabledLanguageCodes();
|
|
13
|
+
|
|
14
|
+
return computed(() => codes.value.length
|
|
15
|
+
? locales.value.filter(item => codes.value.includes(item.code))
|
|
16
|
+
: locales.value);
|
|
17
|
+
};
|
|
@@ -15,6 +15,7 @@ type LocalePathMock = (path: string, localeCode: string) => string;
|
|
|
15
15
|
export const useSeoLinks = (baseDomain: string, i18n: I18nMock, localePath: LocalePathMock) => {
|
|
16
16
|
const route = useRoute();
|
|
17
17
|
const overrides = useSeoOverrides();
|
|
18
|
+
const enabledCodes = useEnabledLanguageCodes();
|
|
18
19
|
|
|
19
20
|
const allowedParams: string[] = ['page', 'sort', 'categories'];
|
|
20
21
|
const createUrlWithParams = (path: string) => {
|
|
@@ -77,10 +78,14 @@ export const useSeoLinks = (baseDomain: string, i18n: I18nMock, localePath: Loca
|
|
|
77
78
|
});
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// локаль, выключенная на сайте, отдаёт 404 (server/middleware/locale-guard.ts) —
|
|
82
|
+
// такие hreflang вели бы на несуществующие страницы
|
|
83
|
+
const isEnabled = (code: string) => !enabledCodes.value.length || enabledCodes.value.includes(code);
|
|
84
|
+
|
|
80
85
|
const otherLocales = i18n.locales.value.filter(
|
|
81
86
|
(locale) => {
|
|
82
87
|
const localeCode = typeof locale === 'string' ? locale : locale.code;
|
|
83
|
-
return localeCode !== currentLocaleCode && localeCode !== 'en';
|
|
88
|
+
return localeCode !== currentLocaleCode && localeCode !== 'en' && isEnabled(localeCode);
|
|
84
89
|
}
|
|
85
90
|
);
|
|
86
91
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SSR-плагин: кладёт коды реально включённых на сайте локалей в useState('enabledLanguageCodes').
|
|
2
|
+
// Нужен, чтобы hreflang в app.vue и переключатели языков не предлагали локали, на которые
|
|
3
|
+
// server/middleware/locale-guard.ts отдаёт 404. Раньше список тянул только layout/language.vue,
|
|
4
|
+
// а он рендерится лениво по клику — на SSR не выполнялся никогда.
|
|
5
|
+
// Эндпоинт читает статический SiteDataService и сам падает обратно на runtimeConfig.public.locales.
|
|
6
|
+
export default defineNuxtPlugin({
|
|
7
|
+
parallel: true,
|
|
8
|
+
async setup() {
|
|
9
|
+
const codes = useEnabledLanguageCodes();
|
|
10
|
+
if (codes.value.length) return;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const languages = await $fetch<string[]>('/api/site/languages');
|
|
14
|
+
if (languages?.length) codes.value = languages;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// оставляем пустым — потребители падают обратно на полный список локалей
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
});
|