itube-specs 0.0.954 → 0.0.955

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.
@@ -32,7 +32,7 @@
32
32
  height="14"
33
33
  loading="lazy"
34
34
  >
35
- {{ t('webcams.language_online', { lang: languageCode.toUpperCase(), count: $n(languageCount) }) }}
35
+ {{ t('webcams.language_online', { lang: languageName, count: $n(languageCount) }) }}
36
36
  </span>
37
37
  </div>
38
38
  </template>
@@ -40,6 +40,7 @@
40
40
  <script setup lang="ts">
41
41
  import type { IWebcamCounts } from '../../types';
42
42
  import { LANG_TO_FLAG } from '../../utils/webcams/lang-to-flag';
43
+ import { isoCodeToLanguageName } from '../../utils/iso-code-to-language-name';
43
44
 
44
45
  const props = defineProps<{
45
46
  counts: IWebcamCounts
@@ -51,6 +52,8 @@ const total = computed(() => props.counts.total || 0);
51
52
  const languageCode = computed(() => locale.value.slice(0, 2).toLowerCase());
52
53
  const languageFlag = computed(() => LANG_TO_FLAG[languageCode.value] || languageCode.value);
53
54
  const languageCount = computed(() => props.counts.byLanguages?.[languageCode.value] || 0);
55
+ // язык называем по-человечески («English speaking»), а не кодом («EN speaking») — как в фильтрах
56
+ const languageName = computed(() => isoCodeToLanguageName(languageCode.value, locale.value));
54
57
  </script>
55
58
 
56
59
  <style lang="scss">
@@ -7,12 +7,22 @@ import { useRoute } from 'vue-router';
7
7
  export const useFetchVideos = async (
8
8
  apiMethod: (...args: any[]) => Promise<PaginatedResponse<IVideoCard>>,
9
9
  // defaultOrder всегда непустой: в запрос уходит либо выбранная сортировка, либо дефолтная
10
- { sortItems = sortItemsDefault, defaultOrder = '-country_order' }: { sortItems?: INavLinkItem[], defaultOrder?: string } = {}
10
+ { sortItems = sortItemsDefault, defaultOrder = '-country_order', perPage: perPageOption }: {
11
+ sortItems?: INavLinkItem[],
12
+ defaultOrder?: string,
13
+ /**
14
+ * Сколько роликов просить у API. По умолчанию 48 на десктопе и 12 на мобиле —
15
+ * ровно ячейки сетки. Переопределяют там, где часть ячеек занимает не видео
16
+ * (врезки вебкам-тумб): тогда роликов просят меньше на число врезок, и сетка
17
+ * остаётся кратной колонкам, без неполной строки.
18
+ */
19
+ perPage?: { mobile?: number, desktop?: number },
20
+ } = {}
11
21
  ) => {
12
22
  const route = useRoute();
13
23
 
14
24
  const mobile = !!useState<boolean>('isMobile').value;
15
- const perPage = mobile ? 12 : 48;
25
+ const perPage = (mobile ? perPageOption?.mobile : perPageOption?.desktop) ?? (mobile ? 12 : 48);
16
26
  const lang = useLang() as Language;
17
27
  const filters = computed(() => useGetVideosFilterRequest(route, selectDurationItems, selectAddedItems));
18
28
  const key = AsyncData.Videos;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.954",
4
+ "version": "0.0.955",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isoCodeToLanguageName } from '../iso-code-to-language-name';
3
+
4
+ describe('isoCodeToLanguageName', () => {
5
+ it('отдаёт название языка на своей локали, с заглавной', () => {
6
+ expect(isoCodeToLanguageName('en', 'en')).toBe('English');
7
+ expect(isoCodeToLanguageName('en', 'ru')).toBe('Английский');
8
+ expect(isoCodeToLanguageName('ru', 'ru')).toBe('Русский');
9
+ expect(isoCodeToLanguageName('de', 'de')).toBe('Deutsch');
10
+ });
11
+
12
+ it('регистр и пробелы в коде не мешают', () => {
13
+ expect(isoCodeToLanguageName(' EN ', 'en')).toBe('English');
14
+ });
15
+
16
+ it('нераспознанный код возвращает кодом, пустой — пустым', () => {
17
+ expect(isoCodeToLanguageName('zz', 'en')).toBe('ZZ');
18
+ expect(isoCodeToLanguageName('', 'en')).toBe('');
19
+ });
20
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * ISO 639-1 → название языка на текущей локали средствами ICU: `en` → «English» /
3
+ * «английский», `zh` → «Chinese». Тот же словарь, что у браузеров и ОС, своей таблицы
4
+ * держать не надо.
5
+ *
6
+ * Первая буква приводится к верхнему регистру: у ru/it/es ICU отдаёт название со строчной
7
+ * («русский»), а подпись — начало фразы («Русский — 5 470 онлайн»).
8
+ *
9
+ * Нераспознанный код возвращается кодом в верхнем регистре: подпись вида «EN speaking»
10
+ * лучше пустой, а по возвращённому значению проверять существование языка нельзя.
11
+ */
12
+ export const isoCodeToLanguageName = (isoCode: string, locale: string): string => {
13
+ const code = (isoCode || '').trim().toLowerCase();
14
+ if (!code) return '';
15
+
16
+ const fallback = code.toUpperCase();
17
+
18
+ try {
19
+ const name = new Intl.DisplayNames([locale], { type: 'language' }).of(code);
20
+ // ICU на неизвестном коде возвращает сам код
21
+ if (!name || name.toLowerCase() === code) return fallback;
22
+
23
+ return name.charAt(0).toLocaleUpperCase(locale) + name.slice(1);
24
+ } catch {
25
+ return fallback;
26
+ }
27
+ };