diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts index f8e329c..9e7304b 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -47,7 +47,19 @@ export default defineNuxtConfig({ i18n: { restructureDir: "./src/i18n", defaultLocale: "en", - locales: [{ code: "en", name: "English", file: "en.json" }], + // no_prefix: locale lives in a cookie, no /vi/ path — keeps the + // root /[shortCode] redirect route untouched. + strategy: "no_prefix", + locales: [ + { code: "en", name: "English", file: "en.json" }, + { code: "vi", name: "Tiếng Việt", file: "vi.json" }, + ], + // Detect browser language on first visit, then persist the choice. + detectBrowserLanguage: { + useCookie: true, + cookieKey: "i18n_redirected", + redirectOn: "root", + }, }, // Runtime runtimeConfig: { diff --git a/apps/frontend/src/app/components/Footer.vue b/apps/frontend/src/app/components/Footer.vue index b72673b..7a0505a 100644 --- a/apps/frontend/src/app/components/Footer.vue +++ b/apps/frontend/src/app/components/Footer.vue @@ -19,7 +19,8 @@ const socialLinks: SocialLink[] = [ }, ]; -const footerItems: FooterColumn[] = [ +// Recompute labels on locale change — setLocale() does not re-run setup. +const footerItems = computed(() => [ { title: $t("footer.column.about"), items: [ @@ -63,7 +64,7 @@ const footerItems: FooterColumn[] = [ }, ], }, -]; +]); diff --git a/apps/frontend/src/i18n/locales/vi.json b/apps/frontend/src/i18n/locales/vi.json new file mode 100644 index 0000000..e1f1f68 --- /dev/null +++ b/apps/frontend/src/i18n/locales/vi.json @@ -0,0 +1,141 @@ +{ + "site": { + "url_shortener": { + "title": "rút gọn url - solitar", + "og_title": "Rút Gọn URL - Solitar", + "description": "Tạo url ngắn cho website và trang mạng xã hội của bạn trong vài giây" + }, + "qr_generator": { + "title": "tạo mã qr - solitar", + "og_title": "Trình Tạo Mã QR - Solitar", + "description": "Tạo mã qr cho url của bạn trong vài giây, lưu và chia sẻ ngay lập tức" + }, + "settings": { + "title": "cài đặt - solitar", + "og_title": "Cài Đặt - Solitar", + "description": "Tùy chỉnh trải nghiệm solitar của bạn" + } + }, + + "nav": { + "url_shortener": "rút gọn url", + "qr_generator": "tạo mã qr", + "settings": "cài đặt" + }, + + "footer": { + "column": { + "about": "giới thiệu", + "resource": "tài nguyên", + "legal": "pháp lý" + }, + "item": { + "changelog": "nhật ký thay đổi", + "status": "trạng thái", + "translate": "dịch thuật", + "api": "api", + "terms_of_use": "điều khoản sử dụng", + "privacy_policy": "chính sách bảo mật", + "report": "báo cáo" + } + }, + + "feature": { + "title": "Solitar có những gì?", + "subtitle": "Mọi thứ bạn cần khi làm việc với url", + "item": { + "speed": { + "title": "Nhanh Chóng Mặt", + "description": "Chuyển hướng tức thì, không độ trễ. Không trang trung gian hay quảng cáo chèn ngang." + }, + "analytics": { + "title": "Phân Tích Tôn Trọng Quyền Riêng Tư", + "description": "Theo dõi lượt nhấp và nguồn truy cập mà không thu thập dữ liệu cá nhân người dùng." + }, + "api": { + "title": "Ưu Tiên Lập Trình Viên", + "description": "REST API mạnh mẽ để tự động tạo liên kết ngay trong ứng dụng của bạn." + }, + "expiration": { + "title": "Hết Hạn Linh Hoạt", + "description": "Đặt liên kết tồn tại vĩnh viễn hoặc tự hủy sau một khoảng thời gian nhất định." + }, + "security": { + "title": "Xác Minh Bảo Mật", + "description": "Cảnh báo người dùng khi truy cập các trang web không tuân thủ tiêu chuẩn bảo mật." + }, + "open_source": { + "title": "Mã Nguồn Mở", + "description": "Logic chuyển hướng hoàn toàn minh bạch. Kiểm tra mã nguồn, tự lưu trữ." + } + } + }, + + "form": { + "password": { + "empty": "Mật khẩu không được để trống", + "invalid": "Mật khẩu phải là chuỗi ký tự", + "min": "Mật khẩu phải có ít nhất 3 ký tự", + "max": "Mật khẩu không được vượt quá 255 ký tự" + }, + "url": { + "required": "URL là bắt buộc", + "invalid": "URL không hợp lệ" + }, + "alias": { + "invalid": "Bí danh phải là chuỗi ký tự", + "min": "Bí danh phải có ít nhất 7 ký tự", + "max": "Bí danh không được vượt quá 255 ký tự" + }, + "expire_time": { + "invalid": "Thời gian hết hạn phải là số", + "min": "Thời gian hết hạn phải lớn hơn 0" + } + }, + + "settings": { + "theme": { + "auto": "Tự động", + "light": "Sáng", + "dark": "Tối" + } + }, + + "counter": "{count} liên kết đã được tạo và truy cập {clicks} lần", + + "hero": { + "title": "Công cụ tất-cả-trong-một mà bạn cần.", + "subtitle_1": "Solitar là nền tảng mã nguồn mở, mạnh mẽ, dùng hằng ngày, giúp bạn rút gọn liên kết dài và tạo mã qr tức thì.", + "subtitle_2": "Solitar mang đến chuyển hướng tức thì không quảng cáo chèn ngang, tự động hóa quy trình với REST API và đặt thời hạn linh hoạt cho liên kết của bạn." + }, + + "not_secure_warning": { + "title": "Kết nối của bạn không an toàn", + "description": "Liên kết bạn sắp truy cập không dùng HTTPS, nghĩa là kết nối của bạn không được mã hóa. Dữ liệu của bạn có thể bị bên thứ ba chặn lấy.", + "enforce_https": "Bắt buộc HTTPS", + "accept_risk": "Chấp nhận rủi ro & tiếp tục" + }, + + "unlock": { + "title": "URL đã bị khóa", + "description": "Liên kết bạn sắp truy cập đã bị người tạo khóa. Vui lòng nhập mật khẩu để mở khóa" + }, + + "button": { + "download": "tải xuống", + "copy_url": "sao chép url", + "close": "đóng", + "unlock": "mở khóa", + "return": "quay lại" + }, + + "error": { + "url_not_found": "Không tìm thấy URL", + "url_expired": "URL này không còn khả dụng", + "url_disabled": "URL này đã bị vô hiệu hóa do vi phạm điều khoản", + "short_code_conflict": "Bí danh này đã tồn tại.", + "password_protected": "Vui lòng nhập mật khẩu hợp lệ để mở khóa URL này", + "incorrect_password": "Mật khẩu được cung cấp không chính xác.", + "default": "Đã xảy ra lỗi" + } +} diff --git a/compose.yml b/compose.yml index 7396509..a04d371 100644 --- a/compose.yml +++ b/compose.yml @@ -10,7 +10,7 @@ services: - "127.0.0.1:26258:26258" command: start-single-node --insecure --http-addr=0.0.0.0:26258 healthcheck: - test: [ "CMD", "curl", "-f", "http://localhost:26258/health?ready=1" ] + test: ["CMD", "curl", "-f", "http://localhost:26258/health?ready=1"] interval: 3s timeout: 5s retries: 3