601 lines
32 KiB
TypeScript
601 lines
32 KiB
TypeScript
// Vietnamese dictionary — the source of truth for key names (`en.ts` is typed
|
||
// as `Dict`, so a missing or misspelled key there is a typecheck error).
|
||
// Not translated anywhere, in any language: film-sim names, user recipe names,
|
||
// user watermark text — they are proper nouns, see WEB_PLAN.md.
|
||
|
||
export const vi = {
|
||
'lang.name': 'Tiếng Việt',
|
||
|
||
'nav.demo': 'DÙNG THỬ',
|
||
'nav.login': 'ĐĂNG NHẬP',
|
||
'nav.signup': 'ĐĂNG KÝ',
|
||
'nav.logout': 'ĐĂNG XUẤT',
|
||
'nav.account': 'TÀI KHOẢN',
|
||
'nav.theme': 'GIAO DIỆN',
|
||
'nav.language': 'NGÔN NGỮ',
|
||
'nav.tabs': 'TAB',
|
||
'nav.back': 'Về trang chủ',
|
||
'nav.admin': 'QUẢN TRỊ',
|
||
'nav.photos': 'ẢNH CỦA TÔI',
|
||
'nav.profile': 'HỒ SƠ',
|
||
// The phone's one button in the header's corner: every control the bar
|
||
// carries, folded into a panel under it (see TopBar).
|
||
'nav.menu': 'MENU',
|
||
'nav.library': 'THƯ VIỆN',
|
||
'nav.studio': 'Studio',
|
||
'nav.guest': 'KHÁCH',
|
||
|
||
// The install offer (src/ui/InstallPrompt.tsx). The only dialog that asks for
|
||
// something the visitor did not come for, so the copy sells what they get out of
|
||
// it and the refusal is one word away.
|
||
'install.title': 'Cài RecipesCam lên màn hình chính',
|
||
'install.body': 'Cài lên màn hình chính để mở nhanh hơn — mượt mà hơn, offline, toàn màn hình như một app.',
|
||
'install.yes': 'ĐỒNG Ý',
|
||
'install.no': 'KHÔNG CẢM ƠN',
|
||
'install.ios': 'Bấm nút Chia sẻ trên thanh địa chỉ, rồi chọn "Thêm vào màn hình chính".',
|
||
|
||
'act.undo': 'HOÀN TÁC',
|
||
'act.redo': 'LÀM LẠI',
|
||
'act.reset': 'ĐẶT LẠI',
|
||
'act.export': 'XUẤT ẢNH',
|
||
'act.exporting': 'ĐANG XUẤT…',
|
||
'act.upscaling': 'Đang phóng to ảnh {done}/{total}…',
|
||
'act.cancel': 'Huỷ',
|
||
|
||
'drop.title': 'Kéo & thả ảnh vào đây',
|
||
'drop.hint': 'hoặc bấm để chọn ảnh · dán từ clipboard bằng Ctrl+V',
|
||
'drop.replace': 'Mở ảnh',
|
||
'drop.clear': 'Xoá ảnh',
|
||
'drop.peeking': 'ĐANG XEM ẢNH GỐC',
|
||
'drop.peekHint': 'Giữ chuột phải hoặc phím \\ để so ảnh gốc',
|
||
'drop.compare': 'So sánh',
|
||
'drop.keep': 'Lưu ảnh gốc',
|
||
'drop.keepHint': 'Lưu nguyên ảnh gốc chưa chỉnh vào thư viện ảnh của máy. Trình duyệt không chia sẻ được tệp thì ảnh sẽ được tải về.',
|
||
'drop.demoLimit': 'Bản demo: ảnh xuất ra tối đa {px}px và có watermark.',
|
||
|
||
// The live viewfinder. The status line is the one fact the user cannot see
|
||
// for themselves: the preview runs at its own resolution, and how fast the
|
||
// device grades it is what tells them the phone is keeping up.
|
||
'cam.open': 'Mở camera',
|
||
'cam.shot': 'CHỤP',
|
||
'cam.flip': 'Đổi camera trước/sau',
|
||
'cam.starting': 'Đang mở camera…',
|
||
'cam.status': '{w}×{h} · {fps} fps',
|
||
'cam.denied': 'Trình duyệt chưa được cho phép dùng camera. Hãy bật quyền rồi mở lại.',
|
||
'cam.unsupported': 'Trình duyệt không cho dùng camera ở địa chỉ này — cần HTTPS hoặc localhost.',
|
||
'cam.fail': 'Không mở được camera.',
|
||
'cam.shotFail': 'Chụp không thành công — thử lại.',
|
||
|
||
// The histogram overlay. 'HISTOGRAM' is the word Vietnamese photographers use.
|
||
'hist.title': 'Histogram',
|
||
'hist.drag': 'Kéo để di chuyển',
|
||
'hist.hide': 'Ẩn histogram',
|
||
|
||
'tab.presets': 'PRESETS',
|
||
'tab.favorited': 'FAVORITED',
|
||
'tab.photos': 'ẢNH CỦA TÔI',
|
||
'tab.light': 'LIGHT',
|
||
'tab.tools': 'TOOLS',
|
||
'tab.hsl': 'HSL',
|
||
'hsl.hint': 'BẤM PICK RỒI CHỌN MỘT MÀU TRÊN ẢNH',
|
||
'tab.frame': 'FRAME',
|
||
'tab.save': 'LƯU GẦN ĐÂY',
|
||
'tab.create': 'CREATE RECIPES',
|
||
|
||
'create.title': 'TẠO CÔNG THỨC',
|
||
'create.save': 'LƯU CÔNG THỨC',
|
||
'create.export': 'XUẤT TỆP .RECIPE',
|
||
'create.guestHint':
|
||
'Khách: công thức chỉ nằm trong RECIPES tới khi đóng trang. Đăng nhập để lưu vào tài khoản và xuất tệp.',
|
||
'create.userHint':
|
||
'Đã lưu vào tài khoản. Xuất tệp .recipe để chia sẻ — tệp được mã hóa đúng như trên app.',
|
||
|
||
'sec.filmSims': 'PHOTO STYLE',
|
||
'sec.recipes': 'RECIPES',
|
||
'sec.saved': 'ĐÃ LƯU',
|
||
'sec.savedEmpty': 'Chưa lưu công thức nào. Đăng nhập rồi bấm LƯU để đồng bộ.',
|
||
'sec.frame': 'KHUNG',
|
||
'sec.crop': 'CẮT',
|
||
'sec.rotate': 'XOAY',
|
||
'sec.watermark': 'WATERMARK',
|
||
'sec.gps': 'GPS',
|
||
|
||
'wb.temp': 'COLOR TEMP',
|
||
|
||
'frame.none': 'KHÔNG KHUNG',
|
||
'frame.polaroid': 'RETRO INSTANT',
|
||
'frame.classic': 'VIỀN CLASSIC',
|
||
'frame.cinematic': 'ĐIỆN ẢNH',
|
||
'frame.wall': 'WALL FRAME PORTRAIT',
|
||
'frame.wallLand': 'WALL FRAME LANDSCAPE',
|
||
'frame.oldFilm': 'OLD FILM LANDSCAPE',
|
||
'frame.oldFilmPort': 'OLD FILM PORTRAIT',
|
||
'frame.custom': 'KHUNG TỰ CHỌN',
|
||
'frame.import': '+ THÊM KHUNG',
|
||
'frame.customTitle': 'Khung tự thêm (Transparent Frame)',
|
||
'frame.zoom': 'Thu phóng',
|
||
'frame.panX': 'Dịch ngang',
|
||
'frame.panY': 'Dịch dọc',
|
||
'frame.fit': 'Vừa khung',
|
||
'frame.fill': 'Lấp đầy',
|
||
'frame.center': 'Căn giữa',
|
||
'frame.replace': 'Đổi khung',
|
||
'frame.remove': 'Bỏ khung',
|
||
'frame.dragHint': 'Kéo trên ảnh để di chuyển • Cuộn chuột để phóng to/thu nhỏ',
|
||
'frame.analyzing': 'Đang phân tích vùng trong suốt của khung...',
|
||
|
||
'crop.none': 'KHÔNG CẮT',
|
||
'crop.1:1': '1:1',
|
||
'crop.2:3': '2:3',
|
||
'crop.3:2': '3:2',
|
||
'crop.3:4': '3:4',
|
||
'crop.4:3': '4:3',
|
||
'crop.16:9': '16:9',
|
||
'crop.free': 'TỰ DO',
|
||
'crop.apply': 'ÁP DỤNG',
|
||
'crop.cancel': 'HUỶ',
|
||
|
||
'rot.0': '0°',
|
||
'rot.90': '90°',
|
||
'rot.180': '180°',
|
||
'rot.270': '270°',
|
||
|
||
'wm.text': 'CHỮ',
|
||
'wm.color': 'MÀU',
|
||
'wm.size': 'CỠ',
|
||
'wm.gps': 'DẤU GPS',
|
||
'wm.place': 'TÊN ĐỊA ĐIỂM',
|
||
'wm.placePh': 'VD: HÀ NỘI',
|
||
'wm.manual': 'NHẬP TOẠ ĐỘ',
|
||
'wm.lat': 'Vĩ độ',
|
||
'wm.lng': 'Kinh độ',
|
||
'wm.hasExif': 'Đã đọc GPS từ ảnh.',
|
||
'wm.noExif': 'Ảnh không có GPS — nhập tay hoặc lấy vị trí hiện tại.',
|
||
'wm.useLocation': 'DÙNG VỊ TRÍ CỦA TÔI',
|
||
'wm.locating': 'ĐANG LẤY VỊ TRÍ…',
|
||
'wm.hasDevice': 'GPS lấy từ vị trí hiện tại của thiết bị.',
|
||
'wm.hasManual': 'GPS do bạn nhập tay.',
|
||
'wm.locFail': 'Không lấy được vị trí — hãy nhập tay.',
|
||
|
||
'auth.loginTitle': 'Đăng nhập',
|
||
'auth.signupTitle': 'Tạo tài khoản',
|
||
'auth.email': 'Email',
|
||
'auth.password': 'Mật khẩu',
|
||
'auth.passwordHint': 'Ít nhất 8 ký tự',
|
||
'auth.noAccount': 'Chưa có tài khoản? Đăng ký',
|
||
'auth.haveAccount': 'Đã có tài khoản? Đăng nhập',
|
||
'auth.continueDemo': 'Tiếp tục dùng thử',
|
||
'auth.busy': 'Đang xử lý…',
|
||
'auth.loggedInAs': 'Đã đăng nhập: {email}',
|
||
'auth.verifyTitle': 'Xác thực email',
|
||
'auth.verifyBody': 'Đã gửi mã xác thực 6 số tới {email}. Nhập mã vào ô dưới đây — hoặc mở liên kết trong email — để dùng được các tính năng PRO.',
|
||
'auth.verifyProHint': 'Khi chưa xác thực, tài khoản vẫn ở bản cơ bản: lưu công thức, lưu ảnh (tối đa 12) và xuất ảnh trong giới hạn cạnh dài của ảnh đang chỉnh sửa. Các tính năng PRO (HSL, TOOLS, RAW, xuất lớn hơn) cần tài khoản được kích hoạt.',
|
||
'auth.verifySent': 'Đã gửi lại mã xác thực.',
|
||
'auth.codePlaceholder': 'Mã 6 số trong email',
|
||
'auth.verifyBtn': 'XÁC THỰC',
|
||
'auth.codeBad': 'Mã không đúng hoặc đã hết hạn (15 phút).',
|
||
'auth.codeLocked': 'Sai quá nhiều lần — hãy gửi lại email để nhận mã mới.',
|
||
'auth.signupVerifyHint': 'Sau khi đăng ký, hệ thống gửi một mã xác thực 6 số tới email — nhập mã để bật các tính năng PRO.',
|
||
|
||
// The trial notice under the header: which features are being tried out, on
|
||
// whom, and until when.
|
||
'notice.trial': 'Một vài tính năng sẽ được sử dụng thử với người đã đăng ký đến ngày 1/12, sau đó chỉ mở các tính năng này trên ứng dụng android',
|
||
|
||
// The PRO gate. A signed-in account the operator has not activated has the
|
||
// basic tier, so the studio needs one place that says what that means (the
|
||
// bar), one reason per locked tab (the chips), and one answer for a click on
|
||
// either of them.
|
||
'pro.hslChip': 'HSL LÀ TÍNH NĂNG PRO',
|
||
'pro.fixChip': 'FIX LÀ TÍNH NĂNG PRO',
|
||
'pro.gradientChip': 'GRADIENT MASK LÀ TÍNH NĂNG PRO',
|
||
'pro.basicBar': 'Tài khoản {email} đang ở bản cơ bản — tính năng PRO cần tài khoản được kích hoạt.',
|
||
'pro.locked': 'Tính năng PRO — tài khoản chưa được kích hoạt. Nhờ quản trị viên tích "Kích hoạt PRO".',
|
||
'pro.resend': 'GỬI LẠI EMAIL XÁC THỰC',
|
||
'pro.resent': 'ĐÃ GỬI',
|
||
|
||
'save.title': 'Lưu công thức',
|
||
'save.name': 'Tên công thức',
|
||
'save.recent': 'LƯU THAY ĐỔI VÀO RECIPES',
|
||
'save.recentHint': 'Lưu look đang đứng trên màn hình — sim, WB, light, FX và frame — thành một recipe riêng của tài khoản này.',
|
||
'save.recentEmpty': 'Chưa có recipe nào của riêng bạn.',
|
||
'save.delete': 'Xoá recipe này',
|
||
'name.photoTitle': 'ĐẶT TÊN ẢNH',
|
||
'name.photoLabel': 'Tên lưu trong thư mục ảnh, và là tiêu đề trên khung ảnh ở landing page.',
|
||
'name.recipeTitle': 'LƯU VÀO RECIPES',
|
||
'name.recipeLabel': 'Tên cho look này, chỉ trong tài khoản của bạn.',
|
||
'name.required': 'Đặt tên trước đã.',
|
||
'name.duplicate': 'Bạn đã có một recipe mang tên này.',
|
||
'name.save': 'LƯU',
|
||
'name.cancel': 'HUỶ',
|
||
'save.saved': 'Đã lưu "{name}"',
|
||
'save.needLogin': 'Đăng nhập để lưu công thức.',
|
||
'save.photo': 'LƯU ẢNH {n}/{max}',
|
||
'save.photoNeedLogin': 'Đăng nhập để lưu ảnh.',
|
||
'save.photoSaved': 'Đã lưu ảnh {n}/{max}.',
|
||
'save.photoFull': 'Đã đủ {max} ảnh — xoá bớt trong Ảnh của tôi.',
|
||
'save.photoNoMax': 'LƯU ẢNH {n}',
|
||
'save.photoNoSource': 'Chưa có ảnh nào để lưu.',
|
||
'save.photoFailed': 'Không lưu được ảnh: {msg}',
|
||
'myp.empty': 'CHƯA LƯU ẢNH NÀO.',
|
||
'myp.history': 'LỊCH SỬ',
|
||
'myp.historyEmpty': 'CHƯA CÓ MỐC LƯU NÀO TRƯỚC ĐÓ.',
|
||
'myp.consent': 'Cho phép ảnh này lên landing page',
|
||
'myp.remove': 'XOÁ ẢNH',
|
||
'del.title': 'XOÁ ẢNH NÀY',
|
||
'del.warning': 'Xoá ảnh này sẽ xoá tất cả history đã lưu trước đó ({n} look) và không khôi phục được. Cần tải ảnh về trước khi xoá.',
|
||
'del.download': 'TẢI VỀ TRƯỚC',
|
||
'del.confirm': 'XOÁ ẢNH',
|
||
'del.cancel': 'HUỶ',
|
||
|
||
// Xác nhận của CLEAR: giữ lại thành quả theo một trong hai cách, hoặc bỏ hẳn.
|
||
'clear.title': 'Xoá ảnh đang mở?',
|
||
'clear.hint':
|
||
'Ảnh sẽ rời khỏi màn hình làm việc. Lưu vào ẢNH CỦA TÔI để lần sau sửa tiếp, xuất ra tệp ảnh, hoặc xoá mà không giữ lại gì.',
|
||
'clear.save': 'LƯU ẢNH',
|
||
'clear.export': 'XUẤT ẢNH',
|
||
'clear.discard': 'XOÁ KHÔNG LƯU',
|
||
'clear.cancel': 'HUỶ',
|
||
|
||
// Xuất ảnh: kích thước đích. Mức lớn hơn cạnh dài nhất của ảnh đang sửa được
|
||
// model phóng to ngay trên máy (engine/superRes), nên tệp ra đúng số pixel đã hỏi.
|
||
'export.title': 'Xuất ảnh',
|
||
'export.hint':
|
||
'Cạnh dài nhất của ảnh đang sửa: {px}px. Mức lớn hơn sẽ được phóng to ngay trên máy này.',
|
||
'export.same': 'XUẤT KHÔNG ĐỔI',
|
||
'export.2k': 'XUẤT 2K (2048px)',
|
||
'export.4k': 'XUẤT 4K (4096px)',
|
||
'export.customLabel': 'Cạnh dài nhất (px)',
|
||
'export.custom': 'XUẤT',
|
||
'export.cap': 'Tài khoản này xuất tối đa {px}px.',
|
||
'export.share': 'Chia sẻ lên',
|
||
'export.shareHint':
|
||
'Điện thoại: app của mạng mở ra với ảnh đã nằm trong ô đăng. Máy tính: ảnh được copy vào clipboard, dán (Ctrl+V) vào ô đăng vừa mở; nơi không cho dán thì ảnh được tải về để bạn kéo vào.',
|
||
|
||
'photo.uploading': 'ĐANG TẢI…',
|
||
|
||
'photos.title': 'Ảnh của tôi',
|
||
'photos.hint': 'Mỗi tài khoản giữ tối đa {max} ảnh. Ảnh lưu từ studio nằm ở đây và xuất hiện trên dải phim cộng đồng.',
|
||
'photos.count': '{n}/{max} ảnh',
|
||
'photos.countNoMax': '{n} ảnh',
|
||
'photos.hintNoMax':
|
||
'Tài khoản admin không giới hạn số ảnh. Ảnh lưu từ studio nằm ở đây và xuất hiện trên dải phim cộng đồng.',
|
||
'photos.add': 'THÊM ẢNH',
|
||
'photos.empty': 'Chưa có ảnh nào. Mở studio rồi bấm LƯU ẢNH, hoặc thêm ảnh ngay ở đây.',
|
||
'photos.full': 'Đã đủ {max} ảnh. Xoá bớt trước khi lưu tiếp.',
|
||
'photos.delete': 'XOÁ',
|
||
'photos.deleted': 'Đã xoá ảnh.',
|
||
'photos.failed': 'Không thực hiện được: {msg}',
|
||
'photos.noLabels': 'Chưa có nhãn dải phim',
|
||
|
||
'lib.title': 'THƯ VIỆN',
|
||
'lib.locked': 'Thư viện dành cho tài khoản. Đăng nhập để duyệt các thư mục ảnh của bạn.',
|
||
// Nút LIBRARY khi chưa có tài khoản: liên kết không được mời, và đây là lý do.
|
||
'lib.needAccount': 'Thư viện dành cho tài khoản — hãy đăng nhập để mở.',
|
||
'lib.hint':
|
||
'Chọn thư mục ảnh trên máy. Ảnh không được tải lên đâu cả — trang chỉ nhớ đường dẫn và giữ một ảnh thu nhỏ cho mỗi tấm.',
|
||
'lib.add': 'THÊM THƯ MỤC',
|
||
'lib.stop': 'DỪNG QUÉT',
|
||
'lib.rename': 'ĐỔI TÊN THƯ MỤC',
|
||
'lib.renameAsk': 'Tên hiển thị cho thư mục này (thư mục trên máy không bị đổi tên):',
|
||
'lib.update': 'CẬP NHẬT...',
|
||
'lib.rescan': 'QUÉT LẠI...',
|
||
'lib.makeThumbs': 'TẠO ẢNH THU NHỎ',
|
||
'lib.thumbsMade': 'Đã tạo {n} ảnh thu nhỏ cho {folder}.',
|
||
'lib.resume': 'ĐỌC TIẾP {folder}',
|
||
'lib.resumeHint': 'Lượt đọc thư mục này bị cắt ngang. Bấm để đọc tiếp từ chỗ đã dừng.',
|
||
'lib.remove': 'BỎ',
|
||
'lib.reconnect': 'CẤP LẠI QUYỀN',
|
||
'lib.nodeHint': 'Chuột phải để đổi tên, cập nhật, quét lại, tạo ảnh thu nhỏ còn thiếu, thu gọn hay bỏ thư mục.',
|
||
'lib.collapseAll': 'THU GỌN TẤT CẢ',
|
||
'lib.collapse': 'THU GỌN',
|
||
'lib.colHint': 'Chuột phải vào vùng trống của cột này để thêm thư mục.',
|
||
'lib.colWidth': 'Kéo để chỉnh độ rộng cột thư mục (mũi tên trái/phải khi đang chọn)',
|
||
'lib.viewPreview': 'MỘT ẢNH',
|
||
'lib.viewGrid': 'TOÀN BỘ',
|
||
// Cột thời gian bên phải bày ảnh (xem `TimelineRuler` trong `Library.tsx`).
|
||
'lib.rail': 'Dòng thời gian',
|
||
'lib.prev': 'Ảnh trước',
|
||
'lib.next': 'Ảnh sau',
|
||
'lib.subsShow': 'HIỆN CẢ THƯ MỤC CON',
|
||
'lib.subsHide': 'CHỈ THƯ MỤC ĐANG CHỌN',
|
||
'lib.subsHint':
|
||
'Ảnh trong thư mục con: hiện cả nhánh dưới thư mục đang chọn, hoặc chỉ ảnh của chính thư mục đó.',
|
||
'lib.open': 'Mở trong studio',
|
||
'lib.openExplorer': 'Open in window explorer',
|
||
'lib.openLocation': 'Open at location',
|
||
'lib.openStudio': 'Mở trong studio',
|
||
'lib.mergePanorama': 'Ghép Panorama…',
|
||
'lib.mergeHdr': 'Ghép HDR (Chống nhòe)…',
|
||
'lib.copiedPath': 'Đã sao chép đường dẫn: {path}',
|
||
'lib.edited': 'đã chỉnh',
|
||
'lib.reset': 'Đặt lại ảnh về mặc định',
|
||
'lib.size': '{mb} MB',
|
||
'lib.count': '{n} ảnh',
|
||
// Cách đọc bày ảnh: điểm sao là của người xem, nên chữ nói đúng thế — "từ n
|
||
// sao" là mức sàn, không phải mức để nhắm tới.
|
||
'lib.sort': 'SẮP XẾP',
|
||
'lib.sortNewest': 'Mới chụp trước',
|
||
'lib.sortOldest': 'Cũ nhất trước',
|
||
'lib.sortStars': 'Nhiều sao nhất',
|
||
'lib.sortDateTaken': 'Ngày chụp',
|
||
'lib.sortDateEdited': 'Ngày sửa/lưu',
|
||
'lib.sortFileType': 'Loại tệp',
|
||
'lib.sortName': 'Tên tệp',
|
||
'lib.sortAsc': 'Tăng dần',
|
||
'lib.sortDesc': 'Giảm dần',
|
||
'lib.rating': 'XẾP HẠNG',
|
||
'lib.ratingAll': 'Mọi mức sao',
|
||
'lib.ratingAtLeast': 'Từ {n} sao',
|
||
'lib.filterReset': 'Xoá mọi bộ lọc — về lại toàn bộ ảnh',
|
||
'lib.search.label': 'Tìm trên Immich',
|
||
'lib.search.placeholder': 'Tên ảnh hoặc tag…',
|
||
'lib.search.hint': 'Immich: gõ tên ảnh hoặc tên tag rồi Enter — kết quả thành một mục riêng trong cây.',
|
||
'lib.search.noServer': 'Chưa có máy chủ Immich nào — thêm một cái trước.',
|
||
'lib.starHint': 'Cho ảnh này {n} sao',
|
||
'lib.rotateLeft': 'Xoay ảnh sang trái',
|
||
'lib.rotateRight': 'Xoay ảnh sang phải',
|
||
'lib.year': 'NĂM',
|
||
'lib.yearAll': 'Mọi năm',
|
||
'lib.month': 'THÁNG',
|
||
'lib.monthAll': 'Mọi tháng',
|
||
'lib.day': 'NGÀY',
|
||
'lib.dayAll': 'Mọi ngày',
|
||
'lib.hourFrom': 'Chụp từ',
|
||
'lib.hourFromAny': 'Mọi giờ',
|
||
'lib.hourTo': 'Chụp đến',
|
||
'lib.hourToAny': 'Mọi giờ',
|
||
'lib.scanning': 'Đang quét {done}/{total} — thêm {added}…',
|
||
'lib.scanned': 'Đã quét {folder}: {added}/{total} ảnh mới.',
|
||
'lib.empty': 'Chưa có ảnh nào trong thư mục này.',
|
||
'lib.noFolders': 'Chưa có thư mục nào. Bấm THÊM THƯ MỤC để chọn một thư mục ảnh trên máy.',
|
||
'lib.blocked':
|
||
'Một tab khác của ứng dụng đang giữ thư viện ở bản cũ hơn. Hãy đóng tab đó, hoặc tải lại nó, để tab này mở được thư viện.',
|
||
'lib.missing': 'Không mở được {name} — tệp đã bị di chuyển hoặc xoá.',
|
||
'lib.offline':
|
||
'Không mở được {name} — thư mục ảnh chưa kết nối. Cắm lại ổ đĩa rồi thử lại.',
|
||
'lib.failed': 'Không đọc được thư mục này.',
|
||
'lib.unsupported':
|
||
'Trình duyệt này không cho phép app đọc thư mục trên máy (cần Chrome, Edge, Opera hoặc Brave). Ảnh vẫn chỉnh được bình thường bằng cách kéo & thả vào studio.',
|
||
// Bản sao của danh mục: bộ nhớ trình duyệt là chỗ duy nhất mà một profile bị
|
||
// xoá lấy mất phần đã đọc, và hai nút này là cách lấy lại.
|
||
'lib.backup': 'SAO LƯU',
|
||
'lib.restore': 'PHỤC HỒI',
|
||
'lib.backupNone': 'Chưa có thư mục sao lưu — bấm để chọn một thư mục và ghi danh mục vào đó.',
|
||
'lib.backupAt': 'Sao lưu: {folder} · {n} ảnh · {time}',
|
||
'lib.backupBlocked': 'Trình duyệt chưa cho trang ghi vào {folder}. Bấm SAO LƯU và cho phép.',
|
||
'lib.backupPick': 'Chọn thư mục mà danh mục được ghi vào và đọc lại từ đó — tên thư mục chính là tên bản sao lưu.',
|
||
'lib.backupRun': 'Đang sao lưu {done}/{total}…',
|
||
'lib.backupDone': 'Đã sao lưu {n} ảnh, ghi {w} thumbnail.',
|
||
'lib.backupFailed': 'Không ghi được vào thư mục sao lưu ({why}).',
|
||
'lib.restoreConfirm': 'Phục hồi {n} ảnh từ {folder}, ghi lúc {time}? Những ảnh thư viện đã có sẽ được giữ nguyên.',
|
||
'lib.restoreRun': 'Đang phục hồi {done}/{total}…',
|
||
'lib.restoreDone': 'Đã phục hồi {n} ảnh. Thêm lại thư mục ảnh để mỗi ảnh lấy lại được tệp của nó.',
|
||
'lib.restoreNothing': 'Thư mục {folder} không có danh mục sao lưu nào — hãy chọn đúng thư mục đã ghi sao lưu.',
|
||
'lib.restoreFailed': 'Không đọc được thư mục sao lưu.',
|
||
|
||
// Sửa dữ liệu (Library.tsx, chỉ quản trị): đọc lại từng cuộn ảnh về đúng đĩa,
|
||
// để danh mục và đĩa nói cùng một điều.
|
||
'lib.repair': 'SỬA DỮ LIỆU',
|
||
'lib.repairConfirm': 'Đọc lại cả {n} thư mục từ đầu để thư viện khớp với đĩa? Ảnh đã mất sẽ bị gỡ khỏi danh mục.',
|
||
'lib.repairRun': 'Đang sửa dữ liệu thư viện ({n} thư mục)…',
|
||
'lib.repairDone': 'Đã sửa dữ liệu thư viện: đọc lại {n} thư mục.',
|
||
|
||
// Immich (src/engine/immich.ts, src/ui/ImmichDialog.tsx, Library.tsx): một
|
||
// nguồn ảnh nữa bên cạnh các thư mục trên máy — một máy chủ mà tài khoản đã
|
||
// lưu, đọc qua các route của backend. Khoá không bao giờ xuống trang này.
|
||
'lib.immich.add': 'THÊM IMMICH',
|
||
'lib.immich.title': 'THÊM MÁY CHỦ IMMICH',
|
||
'lib.cloud.manage': 'TÀI KHOẢN CLOUD',
|
||
'lib.immich.editTitle': 'SỬA TÀI KHOẢN CLOUD',
|
||
'lib.immich.edit': 'SỬA',
|
||
'lib.immich.albumsChoose': 'CHỌN ALBUM',
|
||
'lib.immich.name': 'Tên hiển thị',
|
||
'lib.immich.nameHint': 'Bỏ trống để lấy tên miền',
|
||
'lib.immich.url': 'Địa chỉ máy chủ',
|
||
'lib.immich.key': 'Khoá API',
|
||
'lib.immich.keyShare': 'Đây là link chia sẻ, không phải API key',
|
||
'lib.immich.keyCheck': 'KIỂM TRA KHOÁ',
|
||
'lib.immich.checking': 'ĐANG KIỂM TRA…',
|
||
'lib.immich.keyHelp': 'Trong Immich: ảnh đại diện ▸ Cài đặt ▸ API Keys ▸ NEW API KEY.',
|
||
'lib.immich.perms':
|
||
'Tick đúng sáu quyền này: album.read, album.statistics, asset.read, asset.view, asset.download, asset.statistics. Muốn đẩy ảnh đã sửa ngược lên cloud thì thêm asset.upload, album.create và albumAsset.create. Không cần gì khác — app không bao giờ xoá hay chia sẻ.',
|
||
'lib.immich.ok': 'Khoá dùng được.',
|
||
'lib.immich.albumsPick': 'Tick những album đưa vào thư viện. Không tick gì nghĩa là lấy tất cả.',
|
||
'lib.immich.albumIds': 'Hoặc dán ID album',
|
||
'lib.immich.albumIdsHint': 'Mỗi dòng một ID, hoặc dán cả đường dẫn …/albums/<id>',
|
||
'lib.immich.badIds': 'Bỏ qua {n} dòng không có ID album.',
|
||
'lib.immich.unknownAlbum': 'Khoá này không thấy album {id}.',
|
||
'lib.immich.newKey': 'Khoá mới (bỏ trống nếu giữ nguyên)',
|
||
'lib.immich.noDownload': 'Khoá này không có quyền tải ảnh gốc — chỉ xem và chỉnh được bản xem trước.',
|
||
'lib.immich.sync': 'ĐỌC LẠI',
|
||
'lib.immich.disconnect': 'NGẮT KẾT NỐI',
|
||
'lib.immich.keep': 'XOÁ KHỎI THƯ VIỆN',
|
||
'lib.immich.failed': 'Không đọc được máy chủ này.',
|
||
'lib.immich.auth': 'Khoá sai hoặc thiếu quyền đọc.',
|
||
'lib.immich.unreachable': 'Không kết nối được tới địa chỉ này.',
|
||
'lib.immich.serverErr': 'Máy chủ trả lời không đọc được.',
|
||
'lib.immich.invalid': 'Cần có địa chỉ và khoá.',
|
||
'lib.immich.empty': 'Khoá này không thấy album nào.',
|
||
'lib.immich.added': 'Đã đọc {n} ảnh từ {folder}.',
|
||
'lib.immich.reading': 'Đang đọc {folder}, còn đọc tiếp…',
|
||
'lib.immich.hint': 'Chuột phải để đọc lại, chọn album, ngắt kết nối hoặc xoá khỏi thư viện.',
|
||
'lib.immich.links': 'Liên kết cloud đã lưu',
|
||
'lib.immich.dup': 'trùng địa chỉ — thư viện bị đọc hai lần',
|
||
'lib.immich.linkForget': 'XOÁ LIÊN KẾT',
|
||
'immich.upload': 'Lưu lên Immich',
|
||
'immich.uploadTitle': 'Tải ảnh đã sửa lên Immich Cloud',
|
||
'immich.uploading': 'ĐANG TẢI LÊN…',
|
||
'immich.uploadSuccess': 'Đã tải ảnh lên Immich Cloud thành công!',
|
||
'immich.uploadFailed': 'Tải lên Immich Cloud thất bại: {msg}',
|
||
'immich.uploadToCloud': 'Tải lên Immich Cloud',
|
||
'immich.noServer': 'Chưa kết nối máy chủ Immich Cloud',
|
||
|
||
// Mobile Cloud Library
|
||
'lib.mobile.photos': 'Ảnh',
|
||
'lib.mobile.search': 'Tìm kiếm',
|
||
'lib.mobile.albums': 'Album',
|
||
'lib.mobile.library': 'Thư viện',
|
||
'lib.mobile.allAlbums': 'Tất cả album',
|
||
'lib.mobile.allPhotos': 'Tất cả ảnh',
|
||
'lib.mobile.addImmich': 'THÊM IMMICH',
|
||
'lib.mobile.addImmichCloud': 'THÊM MÁY CHỦ IMMICH CLOUD',
|
||
'lib.mobile.cloudDesc': 'Trên mobile không hỗ trợ quản lý thư mục máy tính cục bộ. Thư viện cloud giúp bạn mở và chỉnh sửa trực tiếp các kho ảnh Immich Cloud.',
|
||
'lib.mobile.servers': 'MÁY CHỦ CLOUD',
|
||
'lib.mobile.noServers': 'Chưa có máy chủ Immich Cloud nào được kết nối.',
|
||
'lib.mobile.noPhotos': 'Chưa có ảnh nào trong thư mục cloud.',
|
||
'lib.mobile.noAlbums': 'Chưa có album nào.',
|
||
'lib.mobile.connected': 'Đã kết nối',
|
||
'lib.mobile.scroll': 'scroll',
|
||
'lib.mobile.openStudio': 'MỞ TRONG STUDIO',
|
||
'lib.mobile.preview': 'Xem ảnh',
|
||
'lib.mobile.details': 'Chi tiết',
|
||
'lib.mobile.rescan': 'ĐỌC LẠI',
|
||
'lib.mobile.chooseAlbums': 'CHỌN ALBUM',
|
||
'lib.mobile.disconnect': 'NGẮT KẾT NỐI',
|
||
'lib.mobile.searchPlaceholder': 'Tìm kiếm trên Immich (tên ảnh, tag)...',
|
||
|
||
'adm.title': 'Quản trị dải phim',
|
||
'adm.subtitle': 'Ảnh do người dùng đóng góp. Xoá một ảnh để gỡ nó khỏi trang chủ.',
|
||
'adm.upload': 'THÊM ẢNH',
|
||
'adm.uploads': 'Ảnh đã tải lên',
|
||
'adm.sections': 'Mục trên landing',
|
||
'adm.albumAll': 'Tất cả ảnh',
|
||
'adm.filterName': 'Lọc theo tên người dùng',
|
||
'adm.sort': 'Sắp xếp',
|
||
'adm.sortNewest': 'Mới nhất trước',
|
||
'adm.sortRecipe': 'Công thức A→Z',
|
||
'adm.sortRating': 'Xếp hạng cao nhất',
|
||
'adm.rating': 'Xếp hạng',
|
||
'adm.ratingAll': 'Mọi mức xếp hạng',
|
||
'adm.ratingAtLeast': 'Từ {n} sao',
|
||
'adm.recipe': 'Công thức',
|
||
'adm.bigEmpty': 'Chọn một ảnh để xem.',
|
||
'adm.pickStrip': 'Dải phim',
|
||
'adm.pickTester': 'Live preset tester',
|
||
'adm.pickCreator': 'Custom recipe creator',
|
||
'adm.pickQr': 'Thẻ QR',
|
||
'adm.noPreset': 'không có look để chia sẻ',
|
||
'adm.rowEmpty': 'Chưa có gì ở đây.',
|
||
'adm.slotHint': 'Tích vào những mục ảnh sẽ xuất hiện — một ảnh có thể nằm cùng lúc ở nhiều mục, và mỗi mục bốc ngẫu nhiên một ảnh trong nhóm của nó mỗi lần ghé. Bỏ tích hết sẽ gỡ ảnh khỏi landing mà không xoá — chủ ảnh vẫn giữ nó trong MY PHOTOS. Mục QR là mục duy nhất phát công thức: thẻ của nó vẽ một mã tải chính tệp .recipe của ảnh để app trên điện thoại quét vào.',
|
||
'adm.signIn': 'Đăng nhập bằng tài khoản quản trị',
|
||
'adm.notAdmin': 'Tài khoản này không có quyền quản trị.',
|
||
'adm.empty': 'Chưa có ảnh đóng góp nào.',
|
||
'adm.total': '{n} ảnh',
|
||
'adm.owner': 'Người đăng',
|
||
'adm.size': 'Dung lượng',
|
||
'adm.uploaded': 'Thời điểm',
|
||
'adm.delete': 'XOÁ',
|
||
'adm.clearAll': 'XOÁ TẤT CẢ',
|
||
'adm.clearConfirm': 'Xoá toàn bộ {n} ảnh đóng góp? Không hoàn tác được.',
|
||
'adm.removed': 'Đã xoá {n} ảnh.',
|
||
'adm.back': 'Về trang chủ',
|
||
'adm.settings': 'CÀI ĐẶT',
|
||
'adm.tabPictures': 'Ảnh',
|
||
'adm.tabUsers': 'Người dùng',
|
||
'adm.tabProfile': 'Hồ sơ',
|
||
'adm.close': 'Đóng',
|
||
'adm.userPicture': 'Ảnh',
|
||
'adm.userName': 'Tên',
|
||
'adm.userEmail': 'Email',
|
||
'adm.userRole': 'Quyền',
|
||
'adm.userPro': 'Kích hoạt PRO',
|
||
'adm.userPhotos': 'Số ảnh',
|
||
'adm.userJoined': 'Ngày tạo',
|
||
'adm.roleAdmin': 'Quản trị',
|
||
'adm.roleMember': 'Thành viên',
|
||
'adm.viewPhotos': 'XEM ẢNH',
|
||
'adm.noUsers': 'Chưa có tài khoản nào.',
|
||
'adm.profileHint': 'Ảnh đại diện, email và mật khẩu của tài khoản đang đăng nhập. Đổi email hoặc mật khẩu cần mật khẩu hiện tại.',
|
||
'adm.changeAvatar': 'ĐỔI ẢNH ĐẠI DIỆN',
|
||
'adm.currentPassword': 'Mật khẩu hiện tại',
|
||
'adm.newEmail': 'Email mới',
|
||
'adm.newPassword': 'Mật khẩu mới',
|
||
'adm.save': 'LƯU',
|
||
'adm.saved': 'Đã lưu hồ sơ.',
|
||
'adm.userActions': 'Thao tác',
|
||
'adm.userBlock': 'KHOÁ',
|
||
'adm.userUnblock': 'MỞ KHOÁ',
|
||
'adm.userRemove': 'GỠ',
|
||
'adm.userRestore': 'KHÔI PHỤC',
|
||
'adm.userDelete': 'XOÁ HẲN',
|
||
'adm.userBlocked': 'Đã khoá',
|
||
'adm.userRemoved': 'Đã gỡ',
|
||
'adm.userDeleteConfirm': 'Xoá vĩnh viễn tài khoản {email} cùng toàn bộ ảnh và công thức của nó? Không hoàn tác được.',
|
||
'adm.selectAll': 'CHỌN TẤT CẢ',
|
||
'adm.selectNone': 'BỎ CHỌN',
|
||
'adm.deleteSelected': 'XOÁ ĐÃ CHỌN ({n})',
|
||
'adm.userDeleteSelectedConfirm': 'Xoá vĩnh viễn {n} tài khoản đã chọn cùng toàn bộ ảnh và công thức của chúng? Không hoàn tác được.',
|
||
'adm.tabStats': 'THỐNG KÊ',
|
||
'adm.tabBackup': 'SAO LƯU',
|
||
'adm.backupHint': 'Tải toàn bộ dữ liệu của hệ thống — cơ sở dữ liệu tài khoản/công thức và hai thư mục ảnh — thành một tệp .tar.gz. Chính tệp này là gói dữ liệu để chuyển sang máy khác: khôi phục bằng nó.',
|
||
'adm.backupSave': 'TẢI BẢN SAO LƯU',
|
||
'adm.restorePick': 'KHÔI PHỤC TỪ TỆP…',
|
||
'adm.restoreHint': 'Khôi phục thay toàn bộ dữ liệu hiện tại bằng dữ liệu trong tệp, sau đó API tự khởi động lại. Dữ liệu đang chạy được nén lại một bản an toàn trong thư mục data trước khi thay.',
|
||
'adm.restoreConfirm': 'Thay toàn bộ dữ liệu hiện tại bằng tệp {file}? Không hoàn tác được. Một bản an toàn vẫn được giữ trong thư mục data.',
|
||
'adm.restoreRunning': 'Đang khôi phục — API đang khởi động lại…',
|
||
'adm.restoreDone': 'Đã khôi phục xong.',
|
||
'adm.restoreSlow': 'API không trả lời lại. Kiểm tra nhật ký container rồi tải lại trang.',
|
||
|
||
// The traffic screen. Bucket names ('/app', a control's data-key, a browser
|
||
// version) are machine values and stay as they are.
|
||
'stats.days': '{n} NGÀY',
|
||
'stats.hint': 'Lưu lượng trong khoảng đã chọn.',
|
||
'stats.views': 'LƯỢT XEM',
|
||
'stats.clicks': 'LƯỢT NHẤN',
|
||
'stats.visitors': 'KHÁCH TRUY CẬP',
|
||
'stats.timeline': 'LƯU LƯỢNG THEO NGÀY',
|
||
'stats.peak': 'đỉnh',
|
||
'stats.pages': 'TRANG ĐƯỢC XEM',
|
||
'stats.targets': 'TÍNH NĂNG ĐƯỢC NHẤN',
|
||
'stats.countries': 'QUỐC GIA',
|
||
'stats.regions': 'VÙNG MIỀN',
|
||
'stats.cities': 'THÀNH PHỐ',
|
||
'stats.browsers': 'TRÌNH DUYỆT',
|
||
'stats.systems': 'HỆ ĐIỀU HÀNH',
|
||
'stats.devices': 'THIẾT BỊ',
|
||
'stats.empty': 'Chưa có dữ liệu.',
|
||
|
||
'profile.title': 'Hồ sơ của tôi',
|
||
|
||
'err.noWebgl':
|
||
'Trình duyệt không cấp được WebGL — bản dựng này cần WebGL để xử lý ảnh (chạy CPU chậm gấp hàng nghìn lần).',
|
||
'err.decode': 'Không đọc được ảnh này. Thử JPG hoặc PNG khác.',
|
||
'err.raw': 'Không tráng được file RAW này. Thử lại, hoặc dùng bản JPG.',
|
||
'err.generic': 'Có lỗi xảy ra: {msg}',
|
||
'err.offline': 'Không gọi được máy chủ.',
|
||
|
||
// The offline bar (src/ui/OfflineBar.tsx). Says both halves: the server is
|
||
// gone, and what is on the screen is the copy this machine kept.
|
||
'offline.notice': 'Mất kết nối máy chủ — đang xem bản đã lưu trên máy.',
|
||
'offline.queued': 'máy chủ không tới được, nên thay đổi này đã lưu trên máy và sẽ gửi khi kết nối lại.',
|
||
'offline.pending': 'Chưa gửi được: {n}',
|
||
|
||
// A folder the reader pointed at that is already in the library. The picker
|
||
// gives no name back to compare by eye, so the note names the one that is there.
|
||
'lib.dupFolder': 'Thư mục "{folder}" đã có trong thư viện.',
|
||
// A repair the reader cut short, told apart from one that ran to its end.
|
||
'lib.repairHalt': 'Đã dừng sửa dữ liệu: đọc lại {n} thư mục trước khi dừng.',
|
||
|
||
// The crash screen (src/ui/ErrorBoundary.tsx). Rare, and the only thing a
|
||
// report of a blank page will have to carry.
|
||
'crash.title': 'Ứng dụng gặp lỗi',
|
||
'crash.body': 'Đã có lỗi xảy ra. Chi tiết ở dưới — gửi lại nếu còn tiếp diễn.',
|
||
'crash.reload': 'Tải lại',
|
||
|
||
// The one-window guard (src/ui/SingleTab.tsx).
|
||
'tab.title': 'RecipesCam đang mở ở tab khác',
|
||
'tab.body': 'Ứng dụng chỉ chạy một tab một lúc. Dùng tab kia, hoặc chuyển sang tab này.',
|
||
'tab.here': 'Dùng tab này',
|
||
'tab.close': 'Đóng tab này',
|
||
'tab.taken': 'Tab khác vừa mở RecipesCam và tiếp quản.',
|
||
'tab.retake': 'Lấy lại',
|
||
|
||
'meta.exported': 'Đã tải {name} ({kb}KB, 300 ppi)',
|
||
} as const;
|
||
|
||
// `vi` is `as const` so the key set is exact; `Dict` widens the values to
|
||
// string, which is what lets `en.ts` hold different text under the same keys
|
||
// while a missing or misspelled key stays a typecheck error.
|
||
export type Dict = { [K in keyof typeof vi]: string };
|
||
export type MsgKey = keyof Dict;
|