itube-specs 0.0.939 → 0.0.941
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/README.md +4 -2
- package/assets/scss/themes/dark/_theme.scss +1 -0
- package/assets/scss/themes/light/_theme.scss +1 -0
- package/components/filter/chips.vue +33 -1
- package/components/filter/model.vue +7 -3
- package/components/section/nav-links.vue +5 -1
- package/components/ui/pagination.vue +9 -0
- package/components/view/model-videos.vue +1 -1
- package/components/view/models.vue +1 -1
- package/components/view/video.vue +1 -1
- package/components/view/videos.vue +1 -1
- package/components/view/webcams.vue +7 -7
- package/composables/__tests__/use-model-filter-chips.test.ts +36 -0
- package/composables/fetch/use-fetch-playlists-by-id.ts +2 -1
- package/composables/fetch/use-fetch-playlists-get-videos-by-id.ts +2 -1
- package/composables/use-filter-pending.ts +4 -2
- package/composables/use-model-filter-chips.ts +24 -2
- package/i18n/locales/de-DE.json +34 -0
- package/i18n/locales/en-US.json +38 -0
- package/i18n/locales/es-ES.json +34 -0
- package/i18n/locales/fr-FR.json +34 -0
- package/i18n/locales/it-IT.json +34 -0
- package/i18n/locales/ja-JP.json +34 -0
- package/i18n/locales/ko-KR.json +34 -0
- package/i18n/locales/ru-RU.json +34 -0
- package/i18n/locales/zh-CN.json +34 -0
- package/nuxt.config.ts +17 -0
- package/package.json +7 -1
- package/pages/channels/[channel].vue +1 -1
- package/scripts/i18n-check.mjs +197 -0
- package/scripts/i18n-sheet.mjs +258 -0
- package/types/filter-chip-item.d.ts +4 -0
- package/utils/ui-pagination/__tests__/get-pagination-link.test.ts +8 -3
- package/utils/ui-pagination/get-pagination-link.ts +11 -3
package/README.md
CHANGED
|
@@ -183,9 +183,11 @@ ad-конфиг), теперь читается из `runtimeConfig.public` (з
|
|
|
183
183
|
|
|
184
184
|
- **Слой:** компоненты, composables, API-сервисы, доменные утилиты, runtime-константы/хелперы, типы,
|
|
185
185
|
`lib/` (продуктовые данные), `config/` (темы, брейкпоинты), middleware (`auth`/`normalize`/`age`), SSR-плагины
|
|
186
|
-
(`country`/`mobile`/`services`/`sentry`/`adv`), BFF-хендлеры `server/api
|
|
186
|
+
(`country`/`mobile`/`services`/`sentry`/`adv`), BFF-хендлеры `server/api/**`, общие i18n-ключи
|
|
187
|
+
(`i18n/locales/` — навигация, сортировки, UI-обвязка) — всё site-agnostic.
|
|
187
188
|
- **Приложение:** только per-site — **значения** config (флаги/реклама/локали) через `runtimeConfig.public`
|
|
188
|
-
(слой держит пустые контейнеры-заглушки), i18n
|
|
189
|
+
(слой держит пустые контейнеры-заглушки), свои i18n-ключи (SEO/ниша) и оверрайды общих,
|
|
190
|
+
`public/` и **выбор темы** (`theme:` в
|
|
189
191
|
`config/site.config.ts`). Своего SCSS у приложения нет: `assets/scss/` — пустой слот-оверрайд.
|
|
190
192
|
`server/` целиком в слое, включая locale-кластер.
|
|
191
193
|
|
|
@@ -713,6 +713,7 @@ $filter-chips-bg--hover: $color-zinc-800-60;
|
|
|
713
713
|
$filter-chips-close: $color-zinc-500;
|
|
714
714
|
$filter-chips-close--hover: $color-white;
|
|
715
715
|
$filter-chips-label: $color-zinc-500;
|
|
716
|
+
$filter-chips-flag-border: $color-white-20;
|
|
716
717
|
$filter-chips-row-clear-text: $color-zinc-500;
|
|
717
718
|
$filter-chips-row-clear-text--hover: $color-white;
|
|
718
719
|
|
|
@@ -762,6 +762,7 @@ $filter-chips-bg--hover: $color-gray-300;
|
|
|
762
762
|
$filter-chips-close: $color-gray-500;
|
|
763
763
|
$filter-chips-close--hover: $color-gray-950;
|
|
764
764
|
$filter-chips-label: $color-gray-500;
|
|
765
|
+
$filter-chips-flag-border: $color-gray-300;
|
|
765
766
|
$filter-chips-row-clear-text: $color-gray-500;
|
|
766
767
|
$filter-chips-row-clear-text--hover: $color-gray-950;
|
|
767
768
|
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
<!-- Чипса выбранного фильтра: приглушённое название фильтра, значение и сброс по
|
|
1
|
+
<!-- Чипса выбранного фильтра: приглушённое название фильтра, значение и сброс по клику.
|
|
2
|
+
|
|
3
|
+
У языковой чипсы вебкамов вместо названия фильтра идёт флаг страны языка, а подпись
|
|
4
|
+
уже собрана переводом («Russian speaking») — поэтому у неё гасится капитализация слов.
|
|
5
|
+
Файла флага для языка может не быть (Китай, Корея) — тогда чипса остаётся без него. -->
|
|
2
6
|
<template>
|
|
3
7
|
<button
|
|
8
|
+
:class="{'--exact-case': item.flag}"
|
|
4
9
|
class="filter-chips"
|
|
5
10
|
type="button"
|
|
6
11
|
@click="emit('click')"
|
|
7
12
|
>
|
|
13
|
+
<img
|
|
14
|
+
v-if="item.flag && !isFlagBroken"
|
|
15
|
+
:src="`/icons/flags/${item.flag}.svg`"
|
|
16
|
+
:alt="item.flag"
|
|
17
|
+
class="filter-chips__flag"
|
|
18
|
+
width="16"
|
|
19
|
+
height="16"
|
|
20
|
+
loading="lazy"
|
|
21
|
+
@error="isFlagBroken = true"
|
|
22
|
+
>
|
|
8
23
|
<span class="filter-chips__text">
|
|
9
24
|
<span
|
|
10
25
|
v-if="item.label && !withoutLabel"
|
|
@@ -31,6 +46,8 @@ defineProps<{
|
|
|
31
46
|
const emit = defineEmits<{
|
|
32
47
|
(eventName: 'click'): void
|
|
33
48
|
}>();
|
|
49
|
+
|
|
50
|
+
const isFlagBroken = ref(false);
|
|
34
51
|
</script>
|
|
35
52
|
|
|
36
53
|
<style lang="scss">
|
|
@@ -58,6 +75,21 @@ const emit = defineEmits<{
|
|
|
58
75
|
}
|
|
59
76
|
}
|
|
60
77
|
|
|
78
|
+
.filter-chips.--exact-case {
|
|
79
|
+
text-transform: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// «бабл» как у флагов в тумбе модели, только мельче: круг со срезанным левым нижним углом
|
|
83
|
+
.filter-chips__flag {
|
|
84
|
+
flex-shrink: 0;
|
|
85
|
+
width: 16px;
|
|
86
|
+
height: 16px;
|
|
87
|
+
object-fit: cover;
|
|
88
|
+
border: 1px solid $filter-chips-flag-border;
|
|
89
|
+
border-radius: 50%;
|
|
90
|
+
border-end-start-radius: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
61
93
|
// подпись и значение читаются одной фразой, поэтому не отдельный флекс-элемент
|
|
62
94
|
.filter-chips__label {
|
|
63
95
|
padding-right: 4px;
|
|
@@ -78,7 +78,7 @@ const props = defineProps<{
|
|
|
78
78
|
|
|
79
79
|
const model = defineModel<boolean>();
|
|
80
80
|
|
|
81
|
-
const { t, locale } = useI18n();
|
|
81
|
+
const { t, te, locale } = useI18n();
|
|
82
82
|
const route = useRoute();
|
|
83
83
|
const router = useRouter();
|
|
84
84
|
|
|
@@ -108,8 +108,12 @@ const pendingRouteLike = reactive({
|
|
|
108
108
|
},
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
/** Пустая строка вместо имени ключа: у сайта перевода может не быть, и тогда
|
|
112
|
+
* подпись чипсы соберётся без него (см. `useFilterChipsItems`). */
|
|
113
|
+
const translate = (key: string, params?: Record<string, unknown>) => (te(key) ? t(key, params || {}) : '');
|
|
114
|
+
|
|
115
|
+
const { chipsItems } = useFilterChipsItems(filters, route, translate, locale);
|
|
116
|
+
const { chipsItems: pendingChipsItems } = useFilterChipsItems(filters, pendingRouteLike, translate, locale);
|
|
113
117
|
|
|
114
118
|
const quickFilters = computed(() =>
|
|
115
119
|
filters.value
|
|
@@ -41,12 +41,16 @@ function linkTo(item: INavLinkItem, index: number) {
|
|
|
41
41
|
return { path: route.path, query: query };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// путь всегда явный: локацию без него роутер разрешает сам и у нишевых разделов
|
|
45
|
+
// (они живут на алиасах записей) терял префикс — а из `resolve` собирается `href`,
|
|
46
|
+
// так что промах уезжал в разметку ссылки
|
|
47
|
+
if (!item.key) return { path: route.path, query: { ...route.query } };
|
|
45
48
|
|
|
46
49
|
const oldQuery = { ...route.query };
|
|
47
50
|
delete oldQuery['page'];
|
|
48
51
|
|
|
49
52
|
return {
|
|
53
|
+
path: route.path,
|
|
50
54
|
query: {
|
|
51
55
|
...oldQuery,
|
|
52
56
|
[item.key]: item.value
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
<!-- Пагинация: номера страниц, «Предыдущая»/«Следующая» и путь с текущим query.
|
|
2
|
+
|
|
3
|
+
Через `Lazy…` подключать МОЖНО, но только с `hydrate-on-idle`, не с
|
|
4
|
+
`hydrate-on-visible`. Пагинация стоит внизу страницы, а фильтры и сортировка
|
|
5
|
+
меняют query из первого экрана: пока компонент не гидрирован, в DOM висит
|
|
6
|
+
разметка от серверного рендера, и гидрация её атрибуты не переписывает. Клик
|
|
7
|
+
мышью после этого работает (навигация идёт по объекту `to`), а «открыть в новой
|
|
8
|
+
вкладке» уходило по устаревшему `href` — без выбранных фильтров и сортировки.
|
|
9
|
+
Ключ `:key` от числа страниц спасал только когда их количество менялось. -->
|
|
1
10
|
<template>
|
|
2
11
|
<nav
|
|
3
12
|
v-if="length > 1"
|
|
@@ -108,7 +108,7 @@ query-параметрах `filter_*`. Формат провайдера при
|
|
|
108
108
|
:total="allPages"
|
|
109
109
|
arrow-icon
|
|
110
110
|
class="_pagination"
|
|
111
|
-
hydrate-on-
|
|
111
|
+
hydrate-on-idle
|
|
112
112
|
/>
|
|
113
113
|
</div>
|
|
114
114
|
</template>
|
|
@@ -213,10 +213,10 @@ const sections = computed<ISwitcherItem[]>(() => [
|
|
|
213
213
|
]);
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
|
-
* Вход в раздел:
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
216
|
+
* Вход в раздел: сразу показываем моделей, которые говорят на языке пользователя.
|
|
217
|
+
* Только при первом заходе за сессию — иначе снятый пользователем фильтр
|
|
218
|
+
* возвращался бы сам, — и только если такой язык у провайдера в выдаче есть
|
|
219
|
+
* (иначе получили бы пустую страницу).
|
|
220
220
|
*/
|
|
221
221
|
const isLanguageFilterApplied = useState('webcams-language-filter', () => false);
|
|
222
222
|
|
|
@@ -225,12 +225,12 @@ onMounted(() => {
|
|
|
225
225
|
isLanguageFilterApplied.value = true;
|
|
226
226
|
|
|
227
227
|
const code = locale.value.slice(0, 2).toLowerCase();
|
|
228
|
-
if (
|
|
228
|
+
if (filterCount.value > 0) return;
|
|
229
229
|
|
|
230
230
|
const languageFilter = filters.value.find(item => item.name === 'language');
|
|
231
231
|
if (!languageFilter?.options.some(option => option.name === code)) return;
|
|
232
232
|
|
|
233
|
-
router.replace({ query: { ...route.query, filter_language: code } });
|
|
233
|
+
router.replace({ path: route.path, query: { ...route.query, filter_language: code } });
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
function translate(key: string, fallback: string) {
|
|
@@ -36,6 +36,42 @@ describe('useFilterChipsItems', () => {
|
|
|
36
36
|
expect(chipsItems.value[0].value).toEqual(['filter_hair_color']);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('языковая чипса: флаг страны языка и подпись переводом, без названия фильтра', () => {
|
|
40
|
+
const filters = ref([{
|
|
41
|
+
name: 'language',
|
|
42
|
+
title: 'Language',
|
|
43
|
+
options: [
|
|
44
|
+
{ name: 'es', title: 'Spanish' },
|
|
45
|
+
{ name: 'ru', title: 'Russian' },
|
|
46
|
+
],
|
|
47
|
+
}]);
|
|
48
|
+
const route = { query: { filter_language: 'es' } };
|
|
49
|
+
const translate = (key: string, params?: Record<string, unknown>) => `${params?.lang} speaking (${key})`;
|
|
50
|
+
const { chipsItems } = useFilterChipsItems(filters, route, translate, ref('en'));
|
|
51
|
+
|
|
52
|
+
expect(chipsItems.value[0]).toEqual({
|
|
53
|
+
title: 'Spanish speaking (webcams.speaking)',
|
|
54
|
+
flag: 'es',
|
|
55
|
+
value: ['filter_language'],
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('языковая чипса: язык без своей страны берёт флаг по мапе, без перевода — только язык', () => {
|
|
60
|
+
const filters = ref([{
|
|
61
|
+
name: 'language',
|
|
62
|
+
title: 'Language',
|
|
63
|
+
options: [{ name: 'ja', title: 'Japanese' }],
|
|
64
|
+
}]);
|
|
65
|
+
const route = { query: { filter_language: 'ja' } };
|
|
66
|
+
const { chipsItems } = useFilterChipsItems(filters, route, () => '', ref('en'));
|
|
67
|
+
|
|
68
|
+
expect(chipsItems.value[0]).toEqual({
|
|
69
|
+
title: 'Japanese',
|
|
70
|
+
flag: 'jp',
|
|
71
|
+
value: ['filter_language'],
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
39
75
|
it('группирует from/to фильтры', () => {
|
|
40
76
|
const filters = ref([{
|
|
41
77
|
name: 'age',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useRoute } from 'vue-router';
|
|
2
|
-
import { AsyncData
|
|
2
|
+
import { AsyncData } from '../../runtime';
|
|
3
|
+
import type { Language } from '../../runtime';
|
|
3
4
|
import type { IPlaylistData } from '../../types';
|
|
4
5
|
|
|
5
6
|
export const useFetchPlaylistsById = async (apiMethod: (...args: any[]) => Promise<IPlaylistData>) => {
|
|
@@ -64,8 +64,10 @@ export function useFilterPending(options: {
|
|
|
64
64
|
options.open.value = false;
|
|
65
65
|
const query = options.encode(values.value, route.query);
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// путь явно: локация без него разрешается роутером, и у нишевых разделов
|
|
68
|
+
// (алиасы записей) префикс ниши пропадал
|
|
69
|
+
if (options.navigate === 'replace') router.replace({ path: route.path, query });
|
|
70
|
+
else router.push({ path: route.path, query });
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
return { values, count, isFiltered, set, remove, replace, reset, apply };
|
|
@@ -2,12 +2,16 @@ import type { IFilterChipItem, IModelFilter, IModelFilterOptions } from '../type
|
|
|
2
2
|
import type { LocationQuery } from '#vue-router';
|
|
3
3
|
import { getMonth } from '../runtime';
|
|
4
4
|
import { buildChipParts } from '../utils/filter-chips/build-chip-parts';
|
|
5
|
+
import { LANG_TO_FLAG } from '../utils/webcams/lang-to-flag';
|
|
5
6
|
import type { Ref } from 'vue';
|
|
6
7
|
|
|
8
|
+
/** Единственный фильтр-язык у нас в вебкамах: у моделей такого поля нет. */
|
|
9
|
+
const LANGUAGE_FILTER = 'language';
|
|
10
|
+
|
|
7
11
|
export function useFilterChipsItems(
|
|
8
12
|
filters: Ref<IModelFilter[]>,
|
|
9
13
|
route: { query: LocationQuery },
|
|
10
|
-
t: (key: string) => string,
|
|
14
|
+
t: (key: string, params?: Record<string, unknown>) => string,
|
|
11
15
|
locale: Ref<string>
|
|
12
16
|
) {
|
|
13
17
|
const chipsItems = computed<IFilterChipItem[]>(() => {
|
|
@@ -83,6 +87,22 @@ export function useFilterChipsItems(
|
|
|
83
87
|
const getValueText = (items: string[]) =>
|
|
84
88
|
[...new Set(items.map(getValue).filter(v => v !== undefined && v !== null && v !== ''))].join(' - ');
|
|
85
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Язык — не просто значение фильтра, а свойство модели: в облаке он читается
|
|
92
|
+
* как «Russian speaking» и с флагом страны языка, без приглушённой подписи
|
|
93
|
+
* фильтра (по макету раздела вебкамов). Флаг лежит файлом по коду СТРАНЫ, а
|
|
94
|
+
* фильтр приходит языком — переводим по мапе; нет файла — чипса просто без
|
|
95
|
+
* флага. Если у сайта ключа перевода ещё нет, остаётся название языка.
|
|
96
|
+
*/
|
|
97
|
+
const buildLanguageChip = (valueText: string) => {
|
|
98
|
+
const code = String(route.query[`filter_${LANGUAGE_FILTER}`] || '').toLowerCase();
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
title: t('webcams.speaking', { lang: valueText }) || valueText,
|
|
102
|
+
flag: LANG_TO_FLAG[code] || code,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
86
106
|
return groups
|
|
87
107
|
.map(item => {
|
|
88
108
|
const filter = findFilter(item);
|
|
@@ -90,7 +110,9 @@ export function useFilterChipsItems(
|
|
|
90
110
|
const valueText = getValueText(item);
|
|
91
111
|
if (!valueText) return null;
|
|
92
112
|
return {
|
|
93
|
-
...
|
|
113
|
+
...(filter.name === LANGUAGE_FILTER
|
|
114
|
+
? buildLanguageChip(valueText)
|
|
115
|
+
: buildChipParts(filter.title, valueText)),
|
|
94
116
|
value: item.map(
|
|
95
117
|
sub => `filter_${sub.replace(/ /g, '_')}`
|
|
96
118
|
),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "Hauptmenü",
|
|
4
|
+
"videos": "Videos",
|
|
5
|
+
"categories": "Kategorien",
|
|
6
|
+
"top_categories": "Top-Kategorien",
|
|
7
|
+
"channels": "Kanäle",
|
|
8
|
+
"live_sex": "Live-Sex",
|
|
9
|
+
"history": "Verlauf",
|
|
10
|
+
"models": "Models",
|
|
11
|
+
"moments": "Momente",
|
|
12
|
+
"playlists": "Playlists",
|
|
13
|
+
"network": "Netzwerk",
|
|
14
|
+
"profile": "Profil",
|
|
15
|
+
"settings": "Einstellungen",
|
|
16
|
+
"my_playlists": "Meine Playlists",
|
|
17
|
+
"back_to_main_page": "Zur Hauptseite zurück",
|
|
18
|
+
"watch_later": "Später ansehen"
|
|
19
|
+
},
|
|
20
|
+
"sort": {
|
|
21
|
+
"network": "Netzwerk",
|
|
22
|
+
"popular": "Beliebt",
|
|
23
|
+
"trending": "Angesagt",
|
|
24
|
+
"new_videos": "Neue Videos",
|
|
25
|
+
"new": "Neu",
|
|
26
|
+
"new_short": "Neu",
|
|
27
|
+
"recommended": "Empfohlen",
|
|
28
|
+
"az": "A-Z",
|
|
29
|
+
"most_videos": "Meiste Videos",
|
|
30
|
+
"last_updated": "Zuletzt aktualisiert",
|
|
31
|
+
"longest": "Längste",
|
|
32
|
+
"longest_short": "Längste"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "Main",
|
|
4
|
+
"videos": "Videos",
|
|
5
|
+
"categories": "Categories",
|
|
6
|
+
"top_categories": "Top categories",
|
|
7
|
+
"channels": "Channels",
|
|
8
|
+
"live_sex": "Live Sex",
|
|
9
|
+
"webcams": "Webcams",
|
|
10
|
+
"history": "History",
|
|
11
|
+
"models": "Models",
|
|
12
|
+
"moments": "Moments",
|
|
13
|
+
"playlists": "Playlists",
|
|
14
|
+
"network": "Network",
|
|
15
|
+
"profile": "Profile",
|
|
16
|
+
"settings": "Settings",
|
|
17
|
+
"my_playlists": "My playlists",
|
|
18
|
+
"library": "Library",
|
|
19
|
+
"back_to_main_page": "Back to Main page",
|
|
20
|
+
"watch_later": "Watch Later"
|
|
21
|
+
},
|
|
22
|
+
"sort": {
|
|
23
|
+
"recent": "Recent",
|
|
24
|
+
"relevant": "Relevant",
|
|
25
|
+
"network": "Network",
|
|
26
|
+
"popular": "Popular",
|
|
27
|
+
"trending": "Trending",
|
|
28
|
+
"new_videos": "New videos",
|
|
29
|
+
"new": "New",
|
|
30
|
+
"new_short": "New",
|
|
31
|
+
"recommended": "Recommended",
|
|
32
|
+
"az": "A-Z",
|
|
33
|
+
"most_videos": "Most videos",
|
|
34
|
+
"last_updated": "Last updated",
|
|
35
|
+
"longest": "Longest",
|
|
36
|
+
"longest_short": "Longest"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "Principal",
|
|
4
|
+
"videos": "Vídeos",
|
|
5
|
+
"categories": "Categorías",
|
|
6
|
+
"top_categories": "Categorías principales",
|
|
7
|
+
"channels": "Canales",
|
|
8
|
+
"models": "Modelos",
|
|
9
|
+
"moments": "Momentos",
|
|
10
|
+
"playlists": "Listas de reproducción",
|
|
11
|
+
"network": "Red",
|
|
12
|
+
"profile": "Perfil",
|
|
13
|
+
"settings": "Configuraciones",
|
|
14
|
+
"my_playlists": "Mis listas de reproducción",
|
|
15
|
+
"back_to_main_page": "Volver a la página principal",
|
|
16
|
+
"history": "Historial",
|
|
17
|
+
"live_sex": "Sexo en vivo",
|
|
18
|
+
"watch_later": "Ver después"
|
|
19
|
+
},
|
|
20
|
+
"sort": {
|
|
21
|
+
"network": "Red",
|
|
22
|
+
"popular": "Popular",
|
|
23
|
+
"trending": "Tendencia",
|
|
24
|
+
"new_videos": "Vídeos nuevos",
|
|
25
|
+
"new": "Nuevo",
|
|
26
|
+
"new_short": "Nuevo",
|
|
27
|
+
"recommended": "Recomendado",
|
|
28
|
+
"az": "A-Z",
|
|
29
|
+
"most_videos": "Más videos",
|
|
30
|
+
"last_updated": "Última actualización",
|
|
31
|
+
"longest": "Más largo",
|
|
32
|
+
"longest_short": "Largos"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "Principal",
|
|
4
|
+
"videos": "Vidéos",
|
|
5
|
+
"categories": "Catégories",
|
|
6
|
+
"top_categories": "Catégories principales",
|
|
7
|
+
"channels": "Chaînes",
|
|
8
|
+
"models": "Modèles",
|
|
9
|
+
"moments": "Moments",
|
|
10
|
+
"playlists": "Listes de lecture",
|
|
11
|
+
"network": "Réseau",
|
|
12
|
+
"profile": "Profil",
|
|
13
|
+
"settings": "Paramètres",
|
|
14
|
+
"my_playlists": "Mes listes de lecture",
|
|
15
|
+
"back_to_main_page": "Retour à la page principale",
|
|
16
|
+
"history": "Historique",
|
|
17
|
+
"live_sex": "Sexe en direct",
|
|
18
|
+
"watch_later": "Regarder plus tard"
|
|
19
|
+
},
|
|
20
|
+
"sort": {
|
|
21
|
+
"network": "Réseau",
|
|
22
|
+
"popular": "Populaire",
|
|
23
|
+
"trending": "Tendance",
|
|
24
|
+
"new_videos": "Nouvelles vidéos",
|
|
25
|
+
"new": "Nouveau",
|
|
26
|
+
"new_short": "Nouveau",
|
|
27
|
+
"recommended": "Recommandé",
|
|
28
|
+
"az": "A-Z",
|
|
29
|
+
"most_videos": "Plus de vidéos",
|
|
30
|
+
"last_updated": "Dernière mise à jour",
|
|
31
|
+
"longest": "Les plus longues",
|
|
32
|
+
"longest_short": "Longues"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "Principale",
|
|
4
|
+
"videos": "Video",
|
|
5
|
+
"categories": "Categorie",
|
|
6
|
+
"top_categories": "Categorie principali",
|
|
7
|
+
"channels": "Canali",
|
|
8
|
+
"models": "Modelli",
|
|
9
|
+
"moments": "Momenti",
|
|
10
|
+
"playlists": "Playlist",
|
|
11
|
+
"network": "Rete",
|
|
12
|
+
"profile": "Profilo",
|
|
13
|
+
"settings": "Impostazioni",
|
|
14
|
+
"my_playlists": "Le mie playlist",
|
|
15
|
+
"back_to_main_page": "Torna alla pagina principale",
|
|
16
|
+
"history": "Cronologia",
|
|
17
|
+
"live_sex": "Sesso dal vivo",
|
|
18
|
+
"watch_later": "Guarda dopo"
|
|
19
|
+
},
|
|
20
|
+
"sort": {
|
|
21
|
+
"network": "Rete",
|
|
22
|
+
"popular": "Popolare",
|
|
23
|
+
"trending": "Tendenza",
|
|
24
|
+
"new_videos": "Nuovi video",
|
|
25
|
+
"new": "Nuovo",
|
|
26
|
+
"new_short": "Nuovo",
|
|
27
|
+
"recommended": "Raccomandato",
|
|
28
|
+
"az": "A-Z",
|
|
29
|
+
"most_videos": "Più video",
|
|
30
|
+
"last_updated": "Ultimo aggiornamento",
|
|
31
|
+
"longest": "Più lungo",
|
|
32
|
+
"longest_short": "Lunghi"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "メイン",
|
|
4
|
+
"videos": "ビデオ",
|
|
5
|
+
"top_categories": "トップカテゴリ",
|
|
6
|
+
"categories": "カテゴリ",
|
|
7
|
+
"channels": "チャンネル",
|
|
8
|
+
"models": "モデル",
|
|
9
|
+
"moments": "瞬間",
|
|
10
|
+
"playlists": "プレイリスト",
|
|
11
|
+
"network": "ネットワーク",
|
|
12
|
+
"profile": "プロフィール",
|
|
13
|
+
"settings": "設定",
|
|
14
|
+
"my_playlists": "マイプレイリスト",
|
|
15
|
+
"back_to_main_page": "メインページに戻る",
|
|
16
|
+
"history": "履歴",
|
|
17
|
+
"live_sex": "ライブ",
|
|
18
|
+
"watch_later": "後で見る"
|
|
19
|
+
},
|
|
20
|
+
"sort": {
|
|
21
|
+
"network": "ネットワーク",
|
|
22
|
+
"popular": "人気",
|
|
23
|
+
"trending": "トレンド",
|
|
24
|
+
"new_videos": "新着ビデオ",
|
|
25
|
+
"new": "新しい",
|
|
26
|
+
"new_short": "新しい",
|
|
27
|
+
"recommended": "おすすめ",
|
|
28
|
+
"az": "A-Z",
|
|
29
|
+
"most_videos": "ビデオ数最多",
|
|
30
|
+
"last_updated": "最終更新",
|
|
31
|
+
"longest": "最長",
|
|
32
|
+
"longest_short": "最長"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "메인",
|
|
4
|
+
"videos": "비디오",
|
|
5
|
+
"categories": "카테고리",
|
|
6
|
+
"top_categories": "상위 카테고리",
|
|
7
|
+
"channels": "채널",
|
|
8
|
+
"models": "모델",
|
|
9
|
+
"moments": "순간",
|
|
10
|
+
"playlists": "재생목록",
|
|
11
|
+
"network": "네트워크",
|
|
12
|
+
"profile": "프로필",
|
|
13
|
+
"settings": "설정",
|
|
14
|
+
"my_playlists": "내 재생목록",
|
|
15
|
+
"back_to_main_page": "메인 페이지로 돌아가기",
|
|
16
|
+
"history": "기록",
|
|
17
|
+
"live_sex": "라이브",
|
|
18
|
+
"watch_later": "나중에 보기"
|
|
19
|
+
},
|
|
20
|
+
"sort": {
|
|
21
|
+
"network": "네트워크",
|
|
22
|
+
"popular": "인기",
|
|
23
|
+
"trending": "트렌드",
|
|
24
|
+
"new_videos": "신규 비디오",
|
|
25
|
+
"new": "새로운",
|
|
26
|
+
"new_short": "새로운",
|
|
27
|
+
"recommended": "추천",
|
|
28
|
+
"az": "A-Z",
|
|
29
|
+
"most_videos": "동영상 수 최다",
|
|
30
|
+
"last_updated": "마지막 업데이트",
|
|
31
|
+
"longest": "가장 긴",
|
|
32
|
+
"longest_short": "가장 긴"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"navigation": {
|
|
3
|
+
"main": "Главная",
|
|
4
|
+
"videos": "Видео",
|
|
5
|
+
"categories": "Категории",
|
|
6
|
+
"top_categories": "Топ категории",
|
|
7
|
+
"channels": "Каналы",
|
|
8
|
+
"live_sex": "Прямой эфир",
|
|
9
|
+
"history": "История",
|
|
10
|
+
"models": "Модели",
|
|
11
|
+
"moments": "Моменты",
|
|
12
|
+
"playlists": "Плейлисты",
|
|
13
|
+
"network": "Сеть",
|
|
14
|
+
"profile": "Профиль",
|
|
15
|
+
"settings": "Настройки",
|
|
16
|
+
"my_playlists": "Мои плейлисты",
|
|
17
|
+
"back_to_main_page": "Вернуться на главную",
|
|
18
|
+
"watch_later": "Смотреть позже"
|
|
19
|
+
},
|
|
20
|
+
"sort": {
|
|
21
|
+
"network": "Сеть",
|
|
22
|
+
"popular": "Популярное",
|
|
23
|
+
"trending": "В тренде",
|
|
24
|
+
"new_videos": "Новые видео",
|
|
25
|
+
"new": "Новое",
|
|
26
|
+
"new_short": "Новые",
|
|
27
|
+
"recommended": "Рекомендуемые",
|
|
28
|
+
"az": "A-Z",
|
|
29
|
+
"most_videos": "Больше видео",
|
|
30
|
+
"last_updated": "Последние",
|
|
31
|
+
"longest": "Самые длинные",
|
|
32
|
+
"longest_short": "Длинные"
|
|
33
|
+
}
|
|
34
|
+
}
|