replace vue-i18n with i18next-vue
Complete the i18n migration by switching runtime setup and remaining components to i18next-vue, and add shared locale constants/helpers for SSR and client language handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
This file provides guidance for AI coding agents working with the Holistream codebase.
|
This file provides guidance for AI coding agents working with the Holistream codebase.
|
||||||
|
hallo
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**Holistream** is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities for content creators.
|
**Holistream** is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities for content creators.
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import SettingsIcon from "@/components/icons/SettingsIcon.vue";
|
|||||||
// import Upload from "@/components/icons/Upload.vue";
|
// import Upload from "@/components/icons/Upload.vue";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { computed, createStaticVNode, ref } from "vue";
|
import { computed, createStaticVNode, ref } from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import NotificationDrawer from "./NotificationDrawer.vue";
|
import NotificationDrawer from "./NotificationDrawer.vue";
|
||||||
|
|
||||||
const className = ":uno: w-12 h-12 p-2 rounded-2xl hover:bg-primary/15 flex press-animated items-center justify-center shrink-0";
|
const className = ":uno: w-12 h-12 p-2 rounded-2xl hover:bg-primary/15 flex press-animated items-center justify-center shrink-0";
|
||||||
const homeHoist = createStaticVNode(`<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />`, 1);
|
const homeHoist = createStaticVNode(`<img class="h-8 w-8" src="/apple-touch-icon.png" alt="Logo" />`, 1);
|
||||||
const notificationPopover = ref<InstanceType<typeof NotificationDrawer>>();
|
const notificationPopover = ref<InstanceType<typeof NotificationDrawer>>();
|
||||||
const isNotificationOpen = ref(false);
|
const isNotificationOpen = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleNotificationClick = (event: Event) => {
|
const handleNotificationClick = (event: Event) => {
|
||||||
notificationPopover.value?.toggle(event);
|
notificationPopover.value?.toggle(event);
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { useUploadQueue } from '@/composables/useUploadQueue';
|
|||||||
import UploadQueueItem from '@/routes/upload/components/UploadQueueItem.vue';
|
import UploadQueueItem from '@/routes/upload/components/UploadQueueItem.vue';
|
||||||
import { useUIState } from '@/stores/uiState';
|
import { useUIState } from '@/stores/uiState';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { items, completeCount, pendingCount, startQueue, removeItem, cancelItem, removeAll } = useUploadQueue();
|
const { items, completeCount, pendingCount, startQueue, removeItem, cancelItem, removeAll } = useUploadQueue();
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isCollapsed = ref(false);
|
const isCollapsed = ref(false);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
|
import NotificationItem from '@/routes/notification/components/NotificationItem.vue';
|
||||||
import { onClickOutside } from '@vueuse/core';
|
import { onClickOutside } from '@vueuse/core';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
// Ensure client-side only rendering to avoid hydration mismatch
|
// Ensure client-side only rendering to avoid hydration mismatch
|
||||||
const isMounted = ref(false);
|
const isMounted = ref(false);
|
||||||
@@ -28,7 +28,7 @@ interface Notification {
|
|||||||
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const drawerRef = ref(null);
|
const drawerRef = ref(null);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Mock notifications data
|
// Mock notifications data
|
||||||
const notifications = computed<Notification[]>(() => [
|
const notifications = computed<Notification[]>(() => [
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import XIcon from '@/components/icons/XIcon.vue';
|
import XIcon from '@/components/icons/XIcon.vue';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
// Ensure client-side only rendering to avoid hydration mismatch
|
// Ensure client-side only rendering to avoid hydration mismatch
|
||||||
const isMounted = ref(false);
|
const isMounted = ref(false);
|
||||||
@@ -26,7 +26,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'close'): void;
|
(e: 'close'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import XCircleIcon from '@/components/icons/XCircleIcon.vue';
|
|||||||
import XIcon from '@/components/icons/XIcon.vue';
|
import XIcon from '@/components/icons/XIcon.vue';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { onBeforeUnmount, watchEffect } from 'vue';
|
import { onBeforeUnmount, watchEffect } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useAppToast, type AppToastSeverity } from '@/composables/useAppToast';
|
import { useAppToast, type AppToastSeverity } from '@/composables/useAppToast';
|
||||||
|
|
||||||
const { toasts, remove } = useAppToast();
|
const { toasts, remove } = useAppToast();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const timers = new Map<string, ReturnType<typeof setTimeout>>();
|
const timers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
|
|
||||||
interface Trend {
|
interface Trend {
|
||||||
@@ -19,7 +19,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
color: 'primary'
|
color: 'primary'
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// const gradients = {
|
// const gradients = {
|
||||||
// primary: 'from-primary/20 to-primary/5',
|
// primary: 'from-primary/20 to-primary/5',
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ const state = reactive<AppConfirmState>({
|
|||||||
|
|
||||||
const requireConfirm = (options: AppConfirmOptions) => {
|
const requireConfirm = (options: AppConfirmOptions) => {
|
||||||
const i18n = getActiveI18n();
|
const i18n = getActiveI18n();
|
||||||
const defaultHeader = i18n?.global.t('confirm.defaultHeader') ?? 'Confirm';
|
const defaultHeader = i18n?.t('confirm.defaultHeader') ?? 'Confirm';
|
||||||
const defaultAccept = i18n?.global.t('confirm.defaultAccept') ?? 'OK';
|
const defaultAccept = i18n?.t('confirm.defaultAccept') ?? 'OK';
|
||||||
const defaultReject = i18n?.global.t('confirm.defaultReject') ?? 'Cancel';
|
const defaultReject = i18n?.t('confirm.defaultReject') ?? 'Cancel';
|
||||||
|
|
||||||
state.visible = true;
|
state.visible = true;
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const abortItem = (id: string) => {
|
|||||||
|
|
||||||
export function useUploadQueue() {
|
export function useUploadQueue() {
|
||||||
const t = (key: string, params?: Record<string, unknown>) =>
|
const t = (key: string, params?: Record<string, unknown>) =>
|
||||||
getActiveI18n()?.global.t(key, params) ?? key;
|
getActiveI18n()?.t(key, params) ?? key;
|
||||||
|
|
||||||
const remainingSlots = computed(() => Math.max(0, MAX_ITEMS - items.value.length));
|
const remainingSlots = computed(() => Math.max(0, MAX_ITEMS - items.value.length));
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ export function useUploadQueue() {
|
|||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(2));
|
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(2));
|
||||||
return `${new Intl.NumberFormat(getActiveI18n()?.global.locale.value === 'vi' ? 'vi-VN' : 'en-US').format(value)} ${sizes[i]}`;
|
return `${new Intl.NumberFormat(getActiveI18n()?.resolvedLanguage === 'vi' ? 'vi-VN' : 'en-US').format(value)} ${sizes[i]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalSize = computed(() => {
|
const totalSize = computed(() => {
|
||||||
|
|||||||
7
src/i18n/constants.ts
Normal file
7
src/i18n/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const supportedLocales = ['en', 'vi'] as const;
|
||||||
|
|
||||||
|
export type SupportedLocale = (typeof supportedLocales)[number];
|
||||||
|
|
||||||
|
export const defaultLocale: SupportedLocale = 'en';
|
||||||
|
|
||||||
|
export const localeCookieKey = 'i18next';
|
||||||
19
src/i18n/index.ts
Normal file
19
src/i18n/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { i18n as I18nInstance } from 'i18next';
|
||||||
|
|
||||||
|
import { getActiveI18nInstance } from '@/lib/translation';
|
||||||
|
|
||||||
|
import { defaultLocale, supportedLocales, type SupportedLocale } from './constants';
|
||||||
|
|
||||||
|
export { supportedLocales, defaultLocale } from './constants';
|
||||||
|
export type { SupportedLocale } from './constants';
|
||||||
|
export { localeCookieKey } from './constants';
|
||||||
|
|
||||||
|
export const normalizeLocale = (locale?: string): SupportedLocale => {
|
||||||
|
if (!locale) return defaultLocale;
|
||||||
|
const normalized = locale.toLowerCase().split('-')[0] as SupportedLocale;
|
||||||
|
return supportedLocales.includes(normalized) ? normalized : defaultLocale;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActiveI18n = (): I18nInstance | undefined => {
|
||||||
|
return getActiveI18nInstance();
|
||||||
|
};
|
||||||
@@ -1,37 +1,98 @@
|
|||||||
import i18next from "i18next";
|
import i18next, { type i18n as I18nInstance } from 'i18next';
|
||||||
import I18NextHttpBackend from "i18next-http-backend";
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import I18NextHttpBackend from 'i18next-http-backend';
|
||||||
const i18n = i18next.createInstance();
|
import { tryGetContext } from 'hono/context-storage';
|
||||||
|
|
||||||
i18n
|
import { defaultLocale, localeCookieKey, supportedLocales, type SupportedLocale } from '@/i18n/constants';
|
||||||
.use(I18NextHttpBackend)
|
|
||||||
.use(LanguageDetector)
|
const runtimeNamespace = 'translation';
|
||||||
.init({
|
|
||||||
supportedLngs: ["en", "vi"],
|
let clientI18n: I18nInstance | undefined;
|
||||||
fallbackLng: "en",
|
|
||||||
defaultNS: "common",
|
const normalizeLanguage = (language?: string): SupportedLocale => {
|
||||||
ns: [
|
if (!language) return defaultLocale;
|
||||||
"common",
|
const normalized = language.toLowerCase().split('-')[0] as SupportedLocale;
|
||||||
"app",
|
return supportedLocales.includes(normalized) ? normalized : defaultLocale;
|
||||||
"auth",
|
};
|
||||||
"nav",
|
|
||||||
"settings",
|
const getLoadPath = () => {
|
||||||
"pageHeader",
|
const cdnBase = import.meta.env.VITE_I18N_CDN_BASE_URL?.trim().replace(/\/+$/, '');
|
||||||
"confirm",
|
if (cdnBase) {
|
||||||
"toast",
|
return `${cdnBase}/locales/{{lng}}/{{lng}}.json`;
|
||||||
"overview",
|
}
|
||||||
"video",
|
return '/locales/{{lng}}/{{lng}}.json';
|
||||||
"notification",
|
};
|
||||||
"upload",
|
|
||||||
"home",
|
const createInstance = () => {
|
||||||
"legal",
|
const instance = i18next.createInstance();
|
||||||
"notFound",
|
|
||||||
],
|
instance.use(I18NextHttpBackend);
|
||||||
|
if (!import.meta.env.SSR) {
|
||||||
|
instance.use(LanguageDetector);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initInstance = async (instance: I18nInstance, language?: string) => {
|
||||||
|
const lng = normalizeLanguage(language);
|
||||||
|
|
||||||
|
if (!instance.isInitialized) {
|
||||||
|
await instance.init({
|
||||||
|
supportedLngs: [...supportedLocales],
|
||||||
|
fallbackLng: defaultLocale,
|
||||||
|
load: 'languageOnly',
|
||||||
|
lng,
|
||||||
|
ns: [runtimeNamespace],
|
||||||
|
defaultNS: runtimeNamespace,
|
||||||
|
fallbackNS: runtimeNamespace,
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: "/locales/{{lng}}/{{ns}}.json", // dynamic fetch JSON
|
loadPath: getLoadPath(),
|
||||||
},
|
},
|
||||||
|
...(import.meta.env.SSR
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
detection: {
|
||||||
|
order: ['cookie', 'navigator', 'htmlTag'],
|
||||||
|
lookupCookie: localeCookieKey,
|
||||||
|
caches: ['cookie'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
export default i18n;
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance.resolvedLanguage !== lng) {
|
||||||
|
await instance.changeLanguage(lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createI18nForRuntime = async (language?: string) => {
|
||||||
|
if (import.meta.env.SSR) {
|
||||||
|
const serverI18n = await initInstance(createInstance(), language);
|
||||||
|
const context = tryGetContext<any>();
|
||||||
|
context?.set?.('i18n', serverI18n);
|
||||||
|
return serverI18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clientI18n) {
|
||||||
|
clientI18n = createInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return initInstance(clientI18n, language);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActiveI18nInstance = () => {
|
||||||
|
if (!import.meta.env.SSR) {
|
||||||
|
return clientI18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = tryGetContext<any>();
|
||||||
|
return context?.get?.('i18n') as I18nInstance | undefined;
|
||||||
|
};
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function getImageAspectRatio(url: string): Promise<AspectInfo> {
|
|||||||
|
|
||||||
|
|
||||||
const getRuntimeLocaleTag = () => {
|
const getRuntimeLocaleTag = () => {
|
||||||
const locale = getActiveI18n()?.global.locale.value;
|
const locale = getActiveI18n()?.resolvedLanguage;
|
||||||
return locale === 'vi' ? 'vi-VN' : 'en-US';
|
return locale === 'vi' ? 'vi-VN' : 'en-US';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { createSSRApp } from 'vue';
|
|||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
import I18NextVue from 'i18next-vue';
|
import I18NextVue from 'i18next-vue';
|
||||||
import i18next from '@/lib/translation';
|
import { createI18nForRuntime } from '@/lib/translation';
|
||||||
|
|
||||||
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
import { withErrorBoundary } from './lib/hoc/withErrorBoundary';
|
||||||
import createAppRouter from './routes';
|
import createAppRouter from './routes';
|
||||||
@@ -32,7 +32,7 @@ export async function createApp(lng: string = 'en') {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
await i18next.init({lng});
|
const i18next = await createI18nForRuntime(lng);
|
||||||
app.use(I18NextVue, { i18next });
|
app.use(I18NextVue, { i18next });
|
||||||
app.use(PiniaColada, {
|
app.use(PiniaColada, {
|
||||||
pinia,
|
pinia,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VueHead } from '@/components/VueHead';
|
import { VueHead } from '@/components/VueHead';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
import { client } from '@/api/client';
|
import { client } from '@/api/client';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
email: ''
|
email: ''
|
||||||
|
|||||||
@@ -80,13 +80,13 @@
|
|||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
|
|||||||
@@ -51,12 +51,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
|
|||||||
@@ -173,13 +173,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const { t, tm } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getFeatureList = (key: string): string[] => {
|
const getFeatureList = (key: string): string[] => {
|
||||||
const localized = tm(key);
|
const localized = t(key, { returnObjects: true });
|
||||||
return Array.isArray(localized) ? localized.map((item) => String(item)) : [];
|
return Array.isArray(localized) ? localized.map((item) => String(item)) : [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Head } from '@unhead/vue/components'
|
import { Head } from '@unhead/vue/components'
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useHead } from '@unhead/vue';
|
import { useHead } from '@unhead/vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const pageContent = computed(() => {
|
const pageContent = computed(() => {
|
||||||
const title = t('legal.privacy.title');
|
const title = t('legal.privacy.title');
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useHead } from '@unhead/vue';
|
import { useHead } from '@unhead/vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const pageContent = computed(() => {
|
const pageContent = computed(() => {
|
||||||
const title = t('legal.terms.title');
|
const title = t('legal.terms.title');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
||||||
import NotificationActions from './components/NotificationActions.vue';
|
import NotificationActions from './components/NotificationActions.vue';
|
||||||
import NotificationList from './components/NotificationList.vue';
|
import NotificationList from './components/NotificationList.vue';
|
||||||
@@ -21,7 +21,7 @@ interface Notification {
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const activeTab = ref('all');
|
const activeTab = ref('all');
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const notifications = ref<Notification[]>([
|
const notifications = ref<Notification[]>([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
@@ -13,7 +13,7 @@ const emit = defineEmits<{
|
|||||||
clearAll: [];
|
clearAll: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import InfoIcon from '@/components/icons/InfoIcon.vue';
|
import InfoIcon from '@/components/icons/InfoIcon.vue';
|
||||||
import CheckCircleIcon from '@/components/icons/CheckCircleIcon.vue';
|
import CheckCircleIcon from '@/components/icons/CheckCircleIcon.vue';
|
||||||
import AlertTriangleIcon from '@/components/icons/AlertTriangleIcon.vue';
|
import AlertTriangleIcon from '@/components/icons/AlertTriangleIcon.vue';
|
||||||
@@ -32,7 +32,7 @@ const emit = defineEmits<{
|
|||||||
delete: [id: string];
|
delete: [id: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const iconComponent = computed(() => {
|
const iconComponent = computed(() => {
|
||||||
const icons: Record<string, any> = {
|
const icons: Record<string, any> = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import NotificationItem from './NotificationItem.vue';
|
import NotificationItem from './NotificationItem.vue';
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
@@ -24,7 +24,7 @@ const emit = defineEmits<{
|
|||||||
delete: [id: string];
|
delete: [id: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { client, type ModelVideo } from '@/api/client';
|
import { client, type ModelVideo } from '@/api/client';
|
||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import NameGradient from './components/NameGradient.vue';
|
import NameGradient from './components/NameGradient.vue';
|
||||||
import QuickActions from './components/QuickActions.vue';
|
import QuickActions from './components/QuickActions.vue';
|
||||||
import RecentVideos from './components/RecentVideos.vue';
|
import RecentVideos from './components/RecentVideos.vue';
|
||||||
@@ -10,7 +10,7 @@ import StatsOverview from './components/StatsOverview.vue';
|
|||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const recentVideos = ref<ModelVideo[]>([]);
|
const recentVideos = ref<ModelVideo[]>([]);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const stats = ref({
|
const stats = ref({
|
||||||
totalVideos: 0,
|
totalVideos: 0,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import Chart from '@/components/icons/Chart.vue';
|
import Chart from '@/components/icons/Chart.vue';
|
||||||
import Credit from '@/components/icons/Credit.vue';
|
import Credit from '@/components/icons/Credit.vue';
|
||||||
import Upload from '@/components/icons/Upload.vue';
|
import Upload from '@/components/icons/Upload.vue';
|
||||||
@@ -17,7 +17,7 @@ defineProps<Props>();
|
|||||||
|
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const quickActions = computed(() => [
|
const quickActions = computed(() => [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ModelVideo } from '@/api/client';
|
import { ModelVideo } from '@/api/client';
|
||||||
import EmptyState from '@/components/dashboard/EmptyState.vue';
|
import EmptyState from '@/components/dashboard/EmptyState.vue';
|
||||||
import { formatDate, formatDuration } from '@/lib/utils';
|
import { formatDate, formatDuration } from '@/lib/utils';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -13,7 +13,7 @@ interface Props {
|
|||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getStatusClass = (status?: string) => {
|
const getStatusClass = (status?: string) => {
|
||||||
switch (status?.toLowerCase()) {
|
switch (status?.toLowerCase()) {
|
||||||
|
|||||||
@@ -28,11 +28,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const isCopied = ref(false);
|
const isCopied = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const url = computed(() => `${location.origin}/ref/${auth.user?.username || ''}`);
|
const url = computed(() => `${location.origin}/ref/${auth.user?.username || ''}`);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import StatsCard from '@/components/dashboard/StatsCard.vue';
|
import StatsCard from '@/components/dashboard/StatsCard.vue';
|
||||||
import { formatBytes } from '@/lib/utils';
|
import { formatBytes } from '@/lib/utils';
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { formatBytes } from '@/lib/utils';
|
import { formatBytes } from '@/lib/utils';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -13,7 +13,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const storagePercentage = computed(() => {
|
const storagePercentage = computed(() => {
|
||||||
return Math.round((props.stats.storageUsed / props.stats.storageLimit) * 100);
|
return Math.round((props.stats.storageUsed / props.stats.storageLimit) * 100);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
||||||
import AppConfirmHost from '@/components/app/AppConfirmHost.vue';
|
import AppConfirmHost from '@/components/app/AppConfirmHost.vue';
|
||||||
@@ -80,7 +80,7 @@ import VideoPlayIcon from '@/components/icons/VideoPlayIcon.vue';
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
// Map tab values to their paths
|
// Map tab values to their paths
|
||||||
const tabPaths: Record<string, string> = {
|
const tabPaths: Record<string, string> = {
|
||||||
profile: '/settings',
|
profile: '/settings',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import CheckIcon from '@/components/icons/CheckIcon.vue';
|
|||||||
import LockIcon from '@/components/icons/LockIcon.vue';
|
import LockIcon from '@/components/icons/LockIcon.vue';
|
||||||
import TelegramIcon from '@/components/icons/TelegramIcon.vue';
|
import TelegramIcon from '@/components/icons/TelegramIcon.vue';
|
||||||
import XIcon from '@/components/icons/XIcon.vue';
|
import XIcon from '@/components/icons/XIcon.vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
dialogVisible: boolean;
|
dialogVisible: boolean;
|
||||||
@@ -31,7 +31,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'disconnect-telegram'): void;
|
(e: 'disconnect-telegram'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChangePassword = () => {
|
const handleChangePassword = () => {
|
||||||
emit('change-password');
|
emit('change-password');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import AppButton from '@/components/app/AppButton.vue';
|
import AppButton from '@/components/app/AppButton.vue';
|
||||||
import AppInput from '@/components/app/AppInput.vue';
|
import AppInput from '@/components/app/AppInput.vue';
|
||||||
import AppProgressBar from '@/components/app/AppProgressBar.vue';
|
import AppProgressBar from '@/components/app/AppProgressBar.vue';
|
||||||
@@ -12,7 +12,7 @@ import UserIcon from '@/components/icons/UserIcon.vue';
|
|||||||
import XIcon from '@/components/icons/XIcon.vue';
|
import XIcon from '@/components/icons/XIcon.vue';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import AppButton from '@/components/app/AppButton.vue';
|
import AppButton from '@/components/app/AppButton.vue';
|
||||||
import AppDialog from '@/components/app/AppDialog.vue';
|
import AppDialog from '@/components/app/AppDialog.vue';
|
||||||
import AppInput from '@/components/app/AppInput.vue';
|
import AppInput from '@/components/app/AppInput.vue';
|
||||||
@@ -33,7 +33,7 @@ const emit = defineEmits<{
|
|||||||
const twoFactorDialogVisible = ref(false);
|
const twoFactorDialogVisible = ref(false);
|
||||||
const twoFactorCode = ref('');
|
const twoFactorCode = ref('');
|
||||||
const twoFactorSecret = ref('JBSWY3DPEHPK3PXP');
|
const twoFactorSecret = ref('JBSWY3DPEHPK3PXP');
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleToggle2FA = async () => {
|
const handleToggle2FA = async () => {
|
||||||
if (!props.twoFactorEnabled) {
|
if (!props.twoFactorEnabled) {
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import TrashIcon from '@/components/icons/TrashIcon.vue';
|
|||||||
import { useAppConfirm } from '@/composables/useAppConfirm';
|
import { useAppConfirm } from '@/composables/useAppConfirm';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const confirm = useAppConfirm();
|
const confirm = useAppConfirm();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
interface VastTemplate {
|
interface VastTemplate {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import { useAppToast } from '@/composables/useAppToast';
|
|||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { useQuery } from '@pinia/colada';
|
import { useQuery } from '@pinia/colada';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { getActiveI18n } from '@/i18n';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const { t, locale } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading } = useQuery({
|
||||||
key: () => ['payments-and-plans'],
|
key: () => ['payments-and-plans'],
|
||||||
@@ -93,7 +94,7 @@ const getStatusLabel = (status: string) => {
|
|||||||
return map[status] || status;
|
return map[status] || status;
|
||||||
};
|
};
|
||||||
|
|
||||||
const currencyFormatter = computed(() => new Intl.NumberFormat(locale.value === 'vi' ? 'vi-VN' : 'en-US', {
|
const currencyFormatter = computed(() => new Intl.NumberFormat(getActiveI18n()?.resolvedLanguage === 'vi' ? 'vi-VN' : 'en-US', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
@@ -118,7 +119,7 @@ const subscribe = async (plan: ModelPlan) => {
|
|||||||
|
|
||||||
paymentHistory.value.unshift({
|
paymentHistory.value.unshift({
|
||||||
id: `inv_${Date.now()}`,
|
id: `inv_${Date.now()}`,
|
||||||
date: new Date().toLocaleDateString(locale.value === 'vi' ? 'vi-VN' : 'en-US', { month: 'short', day: 'numeric', year: 'numeric' }),
|
date: new Date().toLocaleDateString(getActiveI18n()?.resolvedLanguage === 'vi' ? 'vi-VN' : 'en-US', { month: 'short', day: 'numeric', year: 'numeric' }),
|
||||||
amount: plan.price || 0,
|
amount: plan.price || 0,
|
||||||
plan: plan.name || t('settings.billing.unknownPlan'),
|
plan: plan.name || t('settings.billing.unknownPlan'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import SlidersIcon from '@/components/icons/SlidersIcon.vue';
|
|||||||
import TrashIcon from '@/components/icons/TrashIcon.vue';
|
import TrashIcon from '@/components/icons/TrashIcon.vue';
|
||||||
import { useAppConfirm } from '@/composables/useAppConfirm';
|
import { useAppConfirm } from '@/composables/useAppConfirm';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const confirm = useAppConfirm();
|
const confirm = useAppConfirm();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDeleteAccount = () => {
|
const handleDeleteAccount = () => {
|
||||||
confirm.require({
|
confirm.require({
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import TrashIcon from '@/components/icons/TrashIcon.vue';
|
|||||||
import { useAppConfirm } from '@/composables/useAppConfirm';
|
import { useAppConfirm } from '@/composables/useAppConfirm';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const confirm = useAppConfirm();
|
const confirm = useAppConfirm();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const domains = ref([
|
const domains = ref([
|
||||||
{ id: '1', name: 'example.com', addedAt: '2024-01-15' },
|
{ id: '1', name: 'example.com', addedAt: '2024-01-15' },
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import SendIcon from '@/components/icons/SendIcon.vue';
|
|||||||
import TelegramIcon from '@/components/icons/TelegramIcon.vue';
|
import TelegramIcon from '@/components/icons/TelegramIcon.vue';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const notificationSettings = ref({
|
const notificationSettings = ref({
|
||||||
email: true,
|
email: true,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import AppButton from '@/components/app/AppButton.vue';
|
import AppButton from '@/components/app/AppButton.vue';
|
||||||
import AppSwitch from '@/components/app/AppSwitch.vue';
|
import AppSwitch from '@/components/app/AppSwitch.vue';
|
||||||
import CheckIcon from '@/components/icons/CheckIcon.vue';
|
import CheckIcon from '@/components/icons/CheckIcon.vue';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
|
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const playerSettings = ref({
|
const playerSettings = ref({
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import { normalizeLocale } from '@/i18n';
|
|||||||
import { useAppConfirm } from '@/composables/useAppConfirm';
|
import { useAppConfirm } from '@/composables/useAppConfirm';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const confirm = useAppConfirm();
|
const confirm = useAppConfirm();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const selectedLanguage = ref<SupportedLocale>(normalizeLocale((auth.user as any)?.language ?? (auth.user as any)?.locale));
|
const selectedLanguage = ref<SupportedLocale>(normalizeLocale((auth.user as any)?.language ?? (auth.user as any)?.locale));
|
||||||
const languageSaving = ref(false);
|
const languageSaving = ref(false);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useUploadQueue } from '@/composables/useUploadQueue';
|
import { useUploadQueue } from '@/composables/useUploadQueue';
|
||||||
import { useUIState } from '@/stores/uiState';
|
import { useUIState } from '@/stores/uiState';
|
||||||
import RemoteUrlForm from './components/RemoteUrlForm.vue';
|
import RemoteUrlForm from './components/RemoteUrlForm.vue';
|
||||||
@@ -8,7 +8,7 @@ import UploadDropzone from './components/UploadDropzone.vue';
|
|||||||
|
|
||||||
const uiState = useUIState();
|
const uiState = useUIState();
|
||||||
const mode = ref<'local' | 'remote'>('local');
|
const mode = ref<'local' | 'remote'>('local');
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { addFiles, addRemoteUrls, pendingCount, startQueue, remainingSlots, maxItems } = useUploadQueue();
|
const { addFiles, addRemoteUrls, pendingCount, startQueue, remainingSlots, maxItems } = useUploadQueue();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
pendingCount?: number;
|
pendingCount?: number;
|
||||||
@@ -9,7 +9,7 @@ defineProps<{
|
|||||||
|
|
||||||
const category = ref('');
|
const category = ref('');
|
||||||
const visibility = ref('public');
|
const visibility = ref('public');
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{ maxUrls?: number }>();
|
const props = defineProps<{ maxUrls?: number }>();
|
||||||
const urls = ref('');
|
const urls = ref('');
|
||||||
|
|
||||||
const emit = defineEmits<{ submit: [urls: string[]] }>();
|
const emit = defineEmits<{ submit: [urls: string[]] }>();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const limit = props.maxUrls ?? 5;
|
const limit = props.maxUrls ?? 5;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{ maxFiles?: number }>();
|
const props = defineProps<{ maxFiles?: number }>();
|
||||||
const emit = defineEmits<{ filesSelected: [files: FileList] }>();
|
const emit = defineEmits<{ filesSelected: [files: FileList] }>();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isDragOver = ref(false);
|
const isDragOver = ref(false);
|
||||||
let dragCounter = 0;
|
let dragCounter = 0;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: 'local' | 'remote';
|
modelValue: 'local' | 'remote';
|
||||||
@@ -11,7 +11,7 @@ const emit = defineEmits<{
|
|||||||
'update:modelValue': [value: 'local' | 'remote'];
|
'update:modelValue': [value: 'local' | 'remote'];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const modeList = computed<{ id: 'local' | 'remote'; label: string; icon: string }[]>(() => [
|
const modeList = computed<{ id: 'local' | 'remote'; label: string; icon: string }[]>(() => [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import UploadQueueItem from './UploadQueueItem.vue';
|
import UploadQueueItem from './UploadQueueItem.vue';
|
||||||
import type { QueueItem } from '@/composables/useUploadQueue';
|
import type { QueueItem } from '@/composables/useUploadQueue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
items?: QueueItem[];
|
items?: QueueItem[];
|
||||||
@@ -17,7 +17,7 @@ const emit = defineEmits<{
|
|||||||
startQueue: [];
|
startQueue: [];
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import FileUploadType from '@/components/icons/FileUploadType.vue';
|
import FileUploadType from '@/components/icons/FileUploadType.vue';
|
||||||
import type { QueueItem } from '@/composables/useUploadQueue';
|
import type { QueueItem } from '@/composables/useUploadQueue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: QueueItem;
|
item: QueueItem;
|
||||||
@@ -13,7 +13,7 @@ const emit = defineEmits<{
|
|||||||
cancel: [id: string];
|
cancel: [id: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const statusLabel = computed(() => {
|
const statusLabel = computed(() => {
|
||||||
switch (props.item.status) {
|
switch (props.item.status) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { ModelVideo } from '@/api/client';
|
|||||||
import { fetchMockVideoById } from '@/mocks/videos';
|
import { fetchMockVideoById } from '@/mocks/videos';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
videoId: string;
|
videoId: string;
|
||||||
@@ -17,7 +17,7 @@ const toast = useAppToast();
|
|||||||
const video = ref<ModelVideo | null>(null);
|
const video = ref<ModelVideo | null>(null);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const copiedField = ref<string | null>(null);
|
const copiedField = ref<string | null>(null);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const fetchVideo = async () => {
|
const fetchVideo = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { deleteMockVideo, fetchMockVideoById, updateMockVideo } from '@/mocks/vi
|
|||||||
import { useAppConfirm } from '@/composables/useAppConfirm';
|
import { useAppConfirm } from '@/composables/useAppConfirm';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import VideoEditForm from './components/Detail/VideoEditForm.vue';
|
import VideoEditForm from './components/Detail/VideoEditForm.vue';
|
||||||
import VideoHeader from './components/Detail/VideoInfoHeader.vue';
|
import VideoHeader from './components/Detail/VideoInfoHeader.vue';
|
||||||
@@ -16,7 +16,7 @@ const route = useRoute();
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const confirm = useAppConfirm();
|
const confirm = useAppConfirm();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const videoId = route.params.id as string;
|
const videoId = route.params.id as string;
|
||||||
const video = ref<ModelVideo | null>(null);
|
const video = ref<ModelVideo | null>(null);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { ModelVideo } from '@/api/client';
|
|||||||
import { fetchMockVideoById, updateMockVideo } from '@/mocks/videos';
|
import { fetchMockVideoById, updateMockVideo } from '@/mocks/videos';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
videoId: string;
|
videoId: string;
|
||||||
@@ -17,7 +17,7 @@ const toast = useAppToast();
|
|||||||
const video = ref<ModelVideo | null>(null);
|
const video = ref<ModelVideo | null>(null);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
title: '',
|
title: '',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import EmptyState from '@/components/dashboard/EmptyState.vue';
|
|||||||
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
import PageHeader from '@/components/dashboard/PageHeader.vue';
|
||||||
import { fetchMockVideos } from '@/mocks/videos';
|
import { fetchMockVideos } from '@/mocks/videos';
|
||||||
import { createStaticVNode, computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { createStaticVNode, computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { useUploadQueue } from '@/composables/useUploadQueue';
|
import { useUploadQueue } from '@/composables/useUploadQueue';
|
||||||
@@ -23,7 +23,7 @@ const uiState = useUIState();
|
|||||||
const { addFiles, startQueue } = useUploadQueue();
|
const { addFiles, startQueue } = useUploadQueue();
|
||||||
const toast = useAppToast();
|
const toast = useAppToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const videos = ref<ModelVideo[]>([]);
|
const videos = ref<ModelVideo[]>([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import EllipsisVerticalIcon from '@/components/icons/EllipsisVerticalIcon.vue';
|
|||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client';
|
||||||
import { useAppToast } from '@/composables/useAppToast';
|
import { useAppToast } from '@/composables/useAppToast';
|
||||||
import { computed, nextTick, ref } from 'vue';
|
import { computed, nextTick, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import type { RouteLocationRaw } from 'vue-router';
|
import type { RouteLocationRaw } from 'vue-router';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -61,7 +61,7 @@ const isOpen = ref(false);
|
|||||||
const containerRef = ref<HTMLElement>();
|
const containerRef = ref<HTMLElement>();
|
||||||
const menuRef = ref<HTMLElement>();
|
const menuRef = ref<HTMLElement>();
|
||||||
const menuStyle = ref<Record<string, string>>({});
|
const menuStyle = ref<Record<string, string>>({});
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const videoUrl = computed(() => {
|
const videoUrl = computed(() => {
|
||||||
return `${window.location.origin}/videos/${props.video.id}`;
|
return `${window.location.origin}/videos/${props.video.id}`;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title: string;
|
title: string;
|
||||||
@@ -14,7 +14,7 @@ const emit = defineEmits<{
|
|||||||
toggleEdit: [];
|
toggleEdit: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client';
|
||||||
import { formatBytes, getStatusSeverity } from '@/lib/utils';
|
import { formatBytes, getStatusSeverity } from '@/lib/utils';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
import { getActiveI18n } from '@/i18n';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
video: ModelVideo;
|
video: ModelVideo;
|
||||||
@@ -14,7 +15,7 @@ const emit = defineEmits<{
|
|||||||
delete: [];
|
delete: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formatFileSize = (bytes?: number): string => {
|
const formatFileSize = (bytes?: number): string => {
|
||||||
if (!bytes) return '-';
|
if (!bytes) return '-';
|
||||||
@@ -35,7 +36,7 @@ const formatDuration = (seconds?: number): string => {
|
|||||||
const formatDate = (dateStr?: string): string => {
|
const formatDate = (dateStr?: string): string => {
|
||||||
if (!dateStr) return '-';
|
if (!dateStr) return '-';
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
return date.toLocaleString(locale.value === 'vi' ? 'vi-VN' : 'en-US', {
|
return date.toLocaleString(getActiveI18n()?.resolvedLanguage === 'vi' ? 'vi-VN' : 'en-US', {
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
video: ModelVideo;
|
video: ModelVideo;
|
||||||
@@ -11,7 +11,7 @@ const emit = defineEmits<{
|
|||||||
copy: [text: string, label: string];
|
copy: [text: string, label: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleCopy = (text: string, label: string) => {
|
const handleCopy = (text: string, label: string) => {
|
||||||
emit('copy', text, label);
|
emit('copy', text, label);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
video: ModelVideo;
|
video: ModelVideo;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
selectedVideos: ModelVideo[];
|
selectedVideos: ModelVideo[];
|
||||||
@@ -11,7 +11,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'clear'): void;
|
(e: 'clear'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
@@ -19,7 +19,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'search'): void;
|
(e: 'search'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const pageCount = computed(() => Math.ceil(props.total / props.limit) || 1);
|
const pageCount = computed(() => Math.ceil(props.total / props.limit) || 1);
|
||||||
const first = computed(() => Math.min((props.page - 1) * props.limit + 1, props.total));
|
const first = computed(() => Math.min((props.page - 1) * props.limit + 1, props.total));
|
||||||
const last = computed(() => Math.min(props.page * props.limit, props.total));
|
const last = computed(() => Math.min(props.page * props.limit, props.total));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelVideo } from '@/api/client';
|
import type { ModelVideo } from '@/api/client';
|
||||||
import { formatDate, formatDuration, getStatusSeverity } from '@/lib/utils';
|
import { formatDate, formatDuration, getStatusSeverity } from '@/lib/utils';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
import CardPopover from './CardPopover.vue';
|
import CardPopover from './CardPopover.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -15,7 +15,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'delete', videoId: string): void;
|
(e: 'delete', videoId: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const severityClasses: Record<string, string> = {
|
const severityClasses: Record<string, string> = {
|
||||||
success: 'bg-green-100 text-green-800',
|
success: 'bg-green-100 text-green-800',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import PencilIcon from '@/components/icons/PencilIcon.vue';
|
|||||||
import TrashIcon from '@/components/icons/TrashIcon.vue';
|
import TrashIcon from '@/components/icons/TrashIcon.vue';
|
||||||
import VideoIcon from '@/components/icons/VideoIcon.vue';
|
import VideoIcon from '@/components/icons/VideoIcon.vue';
|
||||||
import { formatBytes, formatDate, getStatusSeverity } from '@/lib/utils';
|
import { formatBytes, formatDate, getStatusSeverity } from '@/lib/utils';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useTranslation } from 'i18next-vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
videos: ModelVideo[];
|
videos: ModelVideo[];
|
||||||
@@ -20,7 +20,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'copy', videoId: string): void;
|
(e: 'copy', videoId: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const severityClasses: Record<string, string> = {
|
const severityClasses: Record<string, string> = {
|
||||||
success: 'bg-green-100 text-green-800',
|
success: 'bg-green-100 text-green-800',
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ const resolveUserLocale = (target: Partial<ModelUser> | null | undefined): Suppo
|
|||||||
const applyRuntimeLocale = (locale: SupportedLocale) => {
|
const applyRuntimeLocale = (locale: SupportedLocale) => {
|
||||||
const i18n = getActiveI18n();
|
const i18n = getActiveI18n();
|
||||||
if (!i18n) return;
|
if (!i18n) return;
|
||||||
i18n.global.locale.value = locale;
|
i18n.changeLanguage(locale);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const user = ref<ModelUser | null>(null);
|
const user = ref<ModelUser | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const t = (key: string, params?: Record<string, unknown>) =>
|
const t = (key: string, params?: Record<string, unknown>) =>
|
||||||
getActiveI18n()?.global.t(key, params) ?? key;
|
getActiveI18n()?.t(key, params) ?? key;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
const initialized = ref(false);
|
const initialized = ref(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user