diff --git a/TableProMobile/TableProMobile.xcodeproj/project.pbxproj b/TableProMobile/TableProMobile.xcodeproj/project.pbxproj index abb4c75c9..ce6906d7d 100644 --- a/TableProMobile/TableProMobile.xcodeproj/project.pbxproj +++ b/TableProMobile/TableProMobile.xcodeproj/project.pbxproj @@ -1781,7 +1781,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = TableProWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = D7HJ5TFYCU; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TableProWidget/Info.plist; @@ -1813,7 +1813,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = TableProWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = D7HJ5TFYCU; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TableProWidget/Info.plist; @@ -1969,7 +1969,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = TableProMobile/TableProMobileRelease.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = D7HJ5TFYCU; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -2011,7 +2011,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = TableProMobile/TableProMobileRelease.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = D7HJ5TFYCU; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/TableProMobile/TableProMobile/AppState.swift b/TableProMobile/TableProMobile/AppState.swift index 4ecadd311..167be33f7 100644 --- a/TableProMobile/TableProMobile/AppState.swift +++ b/TableProMobile/TableProMobile/AppState.swift @@ -120,6 +120,15 @@ final class AppState { } } + func reorderGroups(_ reordered: [ConnectionGroup]) { + groups = reordered + groupStorage.save(groups) + for group in reordered { + syncCoordinator.markDirtyGroup(group.id) + } + syncCoordinator.scheduleSyncAfterChange() + } + func deleteGroup(_ groupId: UUID) { groups.removeAll { $0.id == groupId } groupStorage.save(groups) @@ -129,6 +138,7 @@ final class AppState { syncCoordinator.markDirty(connections[index].id) } storage.save(connections) + updateWidgetData() syncCoordinator.markDeletedGroup(groupId) syncCoordinator.scheduleSyncAfterChange() @@ -163,6 +173,7 @@ final class AppState { syncCoordinator.markDirty(connections[index].id) } storage.save(connections) + updateWidgetData() syncCoordinator.markDeletedTag(tagId) syncCoordinator.scheduleSyncAfterChange() diff --git a/TableProMobile/TableProMobile/Localizable.xcstrings b/TableProMobile/TableProMobile/Localizable.xcstrings index 0623ba7aa..3e7ac296f 100644 --- a/TableProMobile/TableProMobile/Localizable.xcstrings +++ b/TableProMobile/TableProMobile/Localizable.xcstrings @@ -1,1417 +1,1658 @@ { - "sourceLanguage" : "en", - "strings" : { - "%lld" : { - + "sourceLanguage": "en", + "strings": { + "%lld": {}, + "%lld / %lld": { + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "%1$lld / %2$lld" + } + } + } + }, + "+%lld more columns": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "+%lld cột khác" + } + } + } + }, + "A fast, lightweight database client for your iPhone and iPad.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Ứng dụng quản lý cơ sở dữ liệu nhanh và nhẹ cho iPhone và iPad." + } + } + } + }, + "Add a database connection to get started.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Thêm kết nối cơ sở dữ liệu để bắt đầu." + } + } + } + }, + "Add Connection": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Thêm kết nối" + } + } + } + }, + "All": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tất cả" + } + } + } + }, + "An unknown error occurred.": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đã xảy ra lỗi không xác định." + } + } + } + }, + "Are you sure you want to delete this row? This action cannot be undone.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bạn có chắc muốn xóa dòng này? Thao tác này không thể hoàn tác." + } + } + } + }, + "Auth Method": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Phương thức xác thực" + } + } + } + }, + "Authentication Failed": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Xác thực thất bại" + } + } + } + }, + "auto-increment": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "tự tăng" + } + } + } + }, + "Cancel": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Hủy" + } + } + } + }, + "Cannot Save": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không thể lưu" + } + } + } + }, + "Check your %@ username and password.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra tên đăng nhập và mật khẩu %@." + } + } + } + }, + "Check your connection settings.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra cài đặt kết nối." + } + } + } + }, + "Check your network connection and server availability.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra kết nối mạng và trạng thái máy chủ." + } + } + } + }, + "Check your query and try again.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra truy vấn và thử lại." + } + } + } + }, + "Check your SQL syntax.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra cú pháp SQL." + } + } + } + }, + "Check your SSH host, port, and credentials.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra máy chủ, cổng và thông tin xác thực SSH." + } + } + } }, - "%lld / %lld" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$lld / %2$lld" + "Check your SSH username, password, or private key.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra tên đăng nhập, mật khẩu hoặc khóa riêng SSH." } } } }, - "+%lld more columns" : { - + "Choose a connection from the sidebar.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chọn kết nối từ thanh bên." + } + } + } }, - "A fast, lightweight database client for your iPhone and iPad." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ứng dụng quản lý cơ sở dữ liệu nhanh và nhẹ cho iPhone và iPad." + "Clear": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Xóa" } } } }, - "Add a database connection to get started." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thêm kết nối cơ sở dữ liệu để bắt đầu." + "Color": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Màu sắc" } } } }, - "Add Connection" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thêm kết nối" + "Columns": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cột" } } } }, - "An unknown error occurred." : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đã xảy ra lỗi không xác định." + "Configuration Error": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Lỗi cấu hình" } } } }, - "Are you sure you want to delete this row? This action cannot be undone." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bạn có chắc muốn xóa dòng này? Thao tác này không thể hoàn tác." + "Connecting to %@...": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang kết nối đến %@..." } } } }, - "Auth Method" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Phương thức xác thực" + "Connection": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kết nối" } } } }, - "Authentication Failed" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xác thực thất bại" + "Connection Failed": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kết nối thất bại" } } } }, - "auto-increment" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "tự tăng" + "Connection refused. The server may not be running or the port is incorrect.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kết nối bị từ chối. Máy chủ có thể chưa chạy hoặc cổng không đúng." } } } }, - "Cancel" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hủy" + "Connection successful": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kết nối thành công" } } } }, - "Cannot Save" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không thể lưu" + "Connections": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kết nối" } } } }, - "Check your %@ username and password." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra tên đăng nhập và mật khẩu %@." + "Continue": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tiếp tục" } } } }, - "Check your connection settings." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra cài đặt kết nối." + "Copy Column Name": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Sao chép tên cột" } } } }, - "Check your network connection and server availability." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra kết nối mạng và trạng thái máy chủ." + "Copy Value": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Sao chép giá trị" } } } }, - "Check your query and try again." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra truy vấn và thử lại." + "Create": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tạo" } } } }, - "Check your SQL syntax." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra cú pháp SQL." + "Create a group to organize your connections.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tạo nhóm để sắp xếp các kết nối." } } } }, - "Check your SSH host, port, and credentials." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra máy chủ, cổng và thông tin xác thực SSH." + "Create a tag to organize your connections.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tạo nhãn để sắp xếp các kết nối." } } } }, - "Check your SSH username, password, or private key." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra tên đăng nhập, mật khẩu hoặc khóa riêng SSH." + "Create New Database": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tạo cơ sở dữ liệu mới" } } } }, - "Choose a connection from the sidebar." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chọn kết nối từ thanh bên." + "Database": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cơ sở dữ liệu" } } } }, - "Clear" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xóa" + "Database File": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tệp cơ sở dữ liệu" } } } }, - "Columns" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cột" + "Database name": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tên cơ sở dữ liệu" } } } }, - "Configuration Error" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lỗi cấu hình" + "Database Name": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tên cơ sở dữ liệu" } } } }, - "Connecting to %@..." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang kết nối đến %@..." + "Database Type": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Loại cơ sở dữ liệu" } } } }, - "Connection" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kết nối" + "Default: %@": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Mặc định: %@" } } } }, - "Connection Failed" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kết nối thất bại" + "Delete": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Xóa" } } } }, - "Connection refused. The server may not be running or the port is incorrect." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kết nối bị từ chối. Máy chủ có thể chưa chạy hoặc cổng không đúng." + "Delete Row": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Xóa dòng" } } } }, - "Connection successful" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kết nối thành công" + "Done": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Xong" } } } }, - "Connections" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kết nối" + "Duplicate": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhân đôi" } } } }, - "Continue" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tiếp tục" + "Edit": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Sửa" } } } }, - "Copy Column Name" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sao chép tên cột" + "Edit Connection": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Sửa kết nối" } } } }, - "Copy Value" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sao chép giá trị" + "Edit Group": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Sửa nhóm" } } } }, - "Create" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tạo" + "Edit Tag": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Sửa nhãn" } } } }, - "Create New Database" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tạo cơ sở dữ liệu mới" + "Enter a name for the new SQLite database.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhập tên cho cơ sở dữ liệu SQLite mới." } } } }, - "Database" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cơ sở dữ liệu" + "Error": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Lỗi" } } } }, - "Database File" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tệp cơ sở dữ liệu" + "Executed queries will appear here.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Các truy vấn đã thực thi sẽ hiển thị ở đây." } } } }, - "Database name" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tên cơ sở dữ liệu" + "Executing...": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang thực thi..." } } } }, - "Database Name" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tên cơ sở dữ liệu" + "Failed to load more rows": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không thể tải thêm dòng" } } } }, - "Database Type" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loại cơ sở dữ liệu" + "Failed to refresh tables": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không thể làm mới bảng" } } } }, - "Default: %@" : { - + "Failed to save credentials.": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không thể lưu thông tin xác thực." + } + } + } }, - "Delete" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xóa" + "Failed to switch database": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không thể chuyển cơ sở dữ liệu" } } } }, - "Delete Row" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xóa dòng" + "Failed to switch schema": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không thể chuyển schema" } } } }, - "Done" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xong" + "Filter by Tag": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Lọc theo nhãn" } } } }, - "Duplicate" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nhân đôi" + "Foreign Keys": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Khóa ngoại" } } } }, - "Edit" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sửa" + "Get Started": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bắt đầu" } } } }, - "Edit Connection" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sửa kết nối" + "Go back and reconnect to the database.": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Quay lại và kết nối lại cơ sở dữ liệu." } } } }, - "Enter a name for the new SQLite database." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nhập tên cho cơ sở dữ liệu SQLite mới." + "Group": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhóm" } } } }, - "Error" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lỗi" + "Group by Folder": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhóm theo thư mục" } } } }, - "Executed queries will appear here." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Các truy vấn đã thực thi sẽ hiển thị ở đây." + "Groups": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhóm" } } } }, - "Executing..." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang thực thi..." + "History": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Lịch sử" } } } }, - "Failed to load more rows" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không thể tải thêm dòng" + "Host": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Máy chủ" } } } }, - "Failed to refresh tables" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không thể làm mới bảng" + "Import connections from your Mac": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhập kết nối từ máy Mac" } } } }, - "Failed to save credentials." : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không thể lưu thông tin xác thực." + "Indexes": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chỉ mục" } } } }, - "Failed to switch database" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không thể chuyển cơ sở dữ liệu" + "Input Method": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Phương thức nhập" } } } }, - "Failed to switch schema" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không thể chuyển schema" + "Insert Row": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Thêm dòng" } } } }, - "Foreign Keys" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Khóa ngoại" + "Keychain Warning": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cảnh báo Keychain" } } } }, - "Get Started" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bắt đầu" + "Load More": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tải thêm" } } } }, - "Go back and reconnect to the database." : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quay lại và kết nối lại cơ sở dữ liệu." + "Loading data...": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang tải dữ liệu..." } } } }, - "History" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lịch sử" + "Loading structure...": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang tải cấu trúc..." } } } }, - "Host" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Máy chủ" + "Loading...": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang tải..." } } } }, - "Import connections from your Mac" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nhập kết nối từ máy Mac" + "Manage Groups": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Quản lý nhóm" } } } }, - "Indexes" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chỉ mục" + "Manage Tags": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Quản lý nhãn" } } } }, - "Input Method" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Phương thức nhập" + "Name": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tên" } } } }, - "Insert Row" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thêm dòng" + "New Connection": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kết nối mới" } } } }, - "Keychain Warning" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cảnh báo Keychain" + "New Database": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cơ sở dữ liệu mới" } } } }, - "Load More" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tải thêm" + "New Group": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhóm mới" } } } }, - "Loading data..." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang tải dữ liệu..." + "New Tag": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhãn mới" } } } }, - "Loading structure..." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang tải cấu trúc..." + "No active database session.": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có phiên cơ sở dữ liệu nào đang hoạt động." } } } }, - "Loading..." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang tải..." + "No Connections": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chưa có kết nối" } } } }, - "Name" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tên" + "No connections match the selected filter.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có kết nối nào khớp với bộ lọc đã chọn." } } } }, - "New Connection" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kết nối mới" + "No Data": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có dữ liệu" } } } }, - "New Database" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cơ sở dữ liệu mới" + "No Foreign Keys": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có khóa ngoại" } } } }, - "No active database session." : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có phiên cơ sở dữ liệu nào đang hoạt động." + "No Groups": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chưa có nhóm" } } } }, - "No Connections" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chưa có kết nối" + "No History": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chưa có lịch sử" } } } }, - "No Data" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có dữ liệu" + "No Indexes": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có chỉ mục" } } } }, - "No Foreign Keys" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có khóa ngoại" + "No Matching Connections": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có kết nối phù hợp" } } } }, - "No History" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chưa có lịch sử" + "No primary key values found.": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không tìm thấy giá trị khóa chính." } } } }, - "No Indexes" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có chỉ mục" + "No Results": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có kết quả" } } } }, - "No primary key values found." : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không tìm thấy giá trị khóa chính." + "No Tables": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không có bảng" } } } }, - "No Results" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có kết quả" + "No Tags": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chưa có nhãn" } } } }, - "No Tables" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có bảng" + "None": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Không" } } } }, - "Not Connected" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chưa kết nối" + "Not Connected": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chưa kết nối" } } } }, - "NULL" : { - + "NULL": {}, + "Nullable": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cho phép NULL" + } + } + } }, - "Nullable" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cho phép NULL" + "OK": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "OK" } } } }, - "OK" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" + "ON DELETE %@": {}, + "ON UPDATE %@": {}, + "Open Database File": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Mở tệp cơ sở dữ liệu" } } } }, - "ON DELETE %@" : { - + "Organization": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tổ chức" + } + } + } }, - "ON UPDATE %@" : { - + "Passphrase (optional)": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Mật khẩu khóa (tùy chọn)" + } + } + } }, - "Open Database File" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mở tệp cơ sở dữ liệu" + "Password": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Mật khẩu" } } } }, - "Passphrase (optional)" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mật khẩu khóa (tùy chọn)" + "Paste private key (PEM format)": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Dán khóa riêng (định dạng PEM)" } } } }, - "Password" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mật khẩu" + "Port": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cổng" } } } }, - "Paste private key (PEM format)" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dán khóa riêng (định dạng PEM)" + "Primary": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chính" } } } }, - "Port" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cổng" + "primary key": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "khóa chính" } } } }, - "Primary" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chính" + "Primary Key": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Khóa chính" } } } }, - "primary key" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "khóa chính" + "Private Key": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Khóa riêng" } } } }, - "Primary Key" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Khóa chính" + "Query": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Truy vấn" } } } }, - "Private Key" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Khóa riêng" + "Query Error": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Lỗi truy vấn" } } } }, - "Query" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Truy vấn" + "Query History": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Lịch sử truy vấn" } } } }, - "Query Error" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lỗi truy vấn" + "read-only": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "chỉ đọc" } } } }, - "Query History" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lịch sử truy vấn" + "Retry": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Thử lại" } } } }, - "read-only" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "chỉ đọc" + "Row %d of %d": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Dòng %d / %d" } } } }, - "Retry" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thử lại" + "Row updated": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đã cập nhật dòng" } } } }, - "Row %d of %d" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dòng %d / %d" + "Run a Query": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chạy truy vấn" } } } }, - "Row updated" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đã cập nhật dòng" + "Save": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Lưu" } } } }, - "Run a Query" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chạy truy vấn" + "Search tables": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tìm bảng" } } } }, - "Save" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lưu" + "Section": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Phần" } } } }, - "Search tables" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tìm bảng" + "SELECT * FROM ...": {}, + "Select a Connection": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chọn kết nối" } } } }, - "Section" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Phần" + "Select Private Key": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chọn khóa riêng" } } } }, - "SELECT * FROM ..." : { - + "Server": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Máy chủ" + } + } + } }, - "Select a Connection" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chọn kết nối" + "Set up a new database connection": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Thiết lập kết nối cơ sở dữ liệu mới" } } } }, - "Select Private Key" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chọn khóa riêng" + "Skip": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bỏ qua" } } } }, - "Server" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Máy chủ" + "SSH Host": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Máy chủ SSH" } } } }, - "Set up a new database connection" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thiết lập kết nối cơ sở dữ liệu mới" + "SSH Password": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Mật khẩu SSH" } } } }, - "Skip" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bỏ qua" + "SSH Port": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cổng SSH" } } } }, - "SSH Host" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Máy chủ SSH" + "SSH Server": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Máy chủ SSH" } } } }, - "SSH Password" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mật khẩu SSH" + "SSH Tunnel": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đường hầm SSH" } } } }, - "SSH Port" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cổng SSH" + "SSH Tunnel Failed": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đường hầm SSH thất bại" } } } }, - "SSH Server" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Máy chủ SSH" + "SSH Username": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tên đăng nhập SSH" } } } }, - "SSH Tunnel" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đường hầm SSH" + "SSL": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "SSL" } } } }, - "SSH Tunnel Failed" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đường hầm SSH thất bại" + "Switching...": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang chuyển..." } } } }, - "SSH Username" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tên đăng nhập SSH" + "Sync from iCloud": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đồng bộ từ iCloud" } } } }, - "SSL" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "SSL" + "Syncing from iCloud...": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang đồng bộ từ iCloud..." } } } }, - "Switching..." : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang chuyển..." + "Tab": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tab" } } } }, - "Sync from iCloud" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đồng bộ từ iCloud" + "Tables": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bảng" } } } }, - "Syncing from iCloud..." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang đồng bộ từ iCloud..." + "Tag": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhãn" } } } }, - "Tab" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tab" + "Tags": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Nhãn" } } } }, - "Tables" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bảng" + "Test Connection": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Kiểm tra kết nối" } } } }, - "Test Connection" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kiểm tra kết nối" + "Testing...": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đang kiểm tra..." } } } }, - "Testing..." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đang kiểm tra..." + "The operation violates a database constraint.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Thao tác vi phạm ràng buộc cơ sở dữ liệu." } } } }, - "The operation violates a database constraint." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thao tác vi phạm ràng buộc cơ sở dữ liệu." + "The server is not responding. Check the host and port.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Máy chủ không phản hồi. Kiểm tra máy chủ và cổng." } } } }, - "The server is not responding. Check the host and port." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Máy chủ không phản hồi. Kiểm tra máy chủ và cổng." + "The SSH server may be unreachable or running a different protocol.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Máy chủ SSH có thể không truy cập được hoặc đang chạy giao thức khác." } } } }, - "The SSH server may be unreachable or running a different protocol." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Máy chủ SSH có thể không truy cập được hoặc đang chạy giao thức khác." + "The SSH tunnel connected but could not forward to the database port.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Đường hầm SSH đã kết nối nhưng không thể chuyển tiếp đến cổng cơ sở dữ liệu." } } } }, - "The SSH tunnel connected but could not forward to the database port." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đường hầm SSH đã kết nối nhưng không thể chuyển tiếp đến cổng cơ sở dữ liệu." + "The table or column does not exist.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bảng hoặc cột không tồn tại." } } } }, - "The table or column does not exist." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bảng hoặc cột không tồn tại." + "This database has no tables.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Cơ sở dữ liệu này không có bảng." } } } }, - "This database has no tables." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cơ sở dữ liệu này không có bảng." + "This table has no foreign key relationships.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bảng này không có quan hệ khóa ngoại." } } } }, - "This table has no foreign key relationships." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bảng này không có quan hệ khóa ngoại." + "This table has no indexes.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bảng này không có chỉ mục." } } } }, - "This table has no indexes." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bảng này không có chỉ mục." + "This table is empty.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bảng này trống." } } } }, - "This table is empty." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bảng này trống." + "This table needs a primary key to identify the row.": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Bảng này cần khóa chính để xác định dòng." } } } }, - "This table needs a primary key to identify the row." : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bảng này cần khóa chính để xác định dòng." + "Ungrouped": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chưa phân nhóm" } } } }, - "Unique" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Duy nhất" + "Unique": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Duy nhất" } } } }, - "Username" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tên đăng nhập" + "Username": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Tên đăng nhập" } } } }, - "Value" : { - + "Value": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Giá trị" + } + } + } }, - "Views" : { - "extractionState" : "stale", - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "View" + "Views": { + "extractionState": "stale", + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "View" } } } }, - "Welcome to TablePro" : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Chào mừng đến TablePro" + "Welcome to TablePro": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Chào mừng đến TablePro" } } } }, - "Write SQL and tap the play button." : { - "localizations" : { - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Viết SQL và nhấn nút chạy." + "Write SQL and tap the play button.": { + "localizations": { + "vi": { + "stringUnit": { + "state": "translated", + "value": "Viết SQL và nhấn nút chạy." } } } } }, - "version" : "1.0" -} \ No newline at end of file + "version": "1.0" +} diff --git a/TableProMobile/TableProMobile/Views/Components/ConnectionColorPicker.swift b/TableProMobile/TableProMobile/Views/Components/ConnectionColorPicker.swift index d7b1501ab..ad9ffcafe 100644 --- a/TableProMobile/TableProMobile/Views/Components/ConnectionColorPicker.swift +++ b/TableProMobile/TableProMobile/Views/Components/ConnectionColorPicker.swift @@ -10,29 +10,31 @@ struct ConnectionColorPicker: View { @Binding var selection: ConnectionColor var body: some View { - HStack(spacing: 12) { - ForEach(ConnectionColor.allCases) { color in - Button { - selection = color - } label: { - ZStack { - Circle() - .fill(Self.swiftUIColor(for: color)) - .frame(width: 28, height: 28) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 12) { + ForEach(ConnectionColor.allCases) { color in + Button { + selection = color + } label: { + ZStack { + Circle() + .fill(Self.swiftUIColor(for: color)) + .frame(width: 28, height: 28) - if selection == color { - Image(systemName: "checkmark") - .font(.caption.bold()) - .foregroundStyle(.white) + if selection == color { + Image(systemName: "checkmark") + .font(.caption.bold()) + .foregroundStyle(.white) + } } + .frame(minWidth: 44, minHeight: 44) + .contentShape(Circle()) } - .frame(minWidth: 44, minHeight: 44) - .contentShape(Circle()) + .buttonStyle(.plain) } - .buttonStyle(.plain) } + .padding(.vertical, 4) } - .padding(.vertical, 4) } static func swiftUIColor(for color: ConnectionColor) -> Color { diff --git a/TableProMobile/TableProMobile/Views/ConnectionFormView.swift b/TableProMobile/TableProMobile/Views/ConnectionFormView.swift index 376bade53..e1ed4cac2 100644 --- a/TableProMobile/TableProMobile/Views/ConnectionFormView.swift +++ b/TableProMobile/TableProMobile/Views/ConnectionFormView.swift @@ -22,8 +22,13 @@ struct ConnectionFormView: View { @State private var database = "" @State private var sslEnabled = false - // SQLite file picker - @State private var showFilePicker = false + // File pickers + enum ActiveFilePicker: Identifiable { + case sqliteDatabase + case sshKey + var id: Int { hashValue } + } + @State private var activeFilePicker: ActiveFilePicker? @State private var selectedFileURL: URL? @State private var showNewDatabaseAlert = false @State private var newDatabaseName = "" @@ -43,7 +48,12 @@ struct ConnectionFormView: View { @State private var sshKeyContent = "" @State private var sshKeyPassphrase = "" @State private var sshKeyInputMode = KeyInputMode.file - @State private var showSSHKeyPicker = false + private var showFilePicker: Binding { + Binding( + get: { activeFilePicker != nil }, + set: { if !$0 { activeFilePicker = nil } } + ) + } enum KeyInputMode: String, CaseIterable { case file = "Import File" @@ -207,13 +217,11 @@ struct ConnectionFormView: View { if let stored = try? appState.secureStore.retrieve(forKey: connKey), !stored.isEmpty { password = stored } - if sshEnabled { - if let sshPwd = try? appState.secureStore.retrieve(forKey: "com.TablePro.sshpassword.\(conn.id.uuidString)"), !sshPwd.isEmpty { - sshPassword = sshPwd - } - if let passphrase = try? appState.secureStore.retrieve(forKey: "com.TablePro.keypassphrase.\(conn.id.uuidString)"), !passphrase.isEmpty { - sshKeyPassphrase = passphrase - } + if let sshPwd = try? appState.secureStore.retrieve(forKey: "com.TablePro.sshpassword.\(conn.id.uuidString)"), !sshPwd.isEmpty { + sshPassword = sshPwd + } + if let passphrase = try? appState.secureStore.retrieve(forKey: "com.TablePro.keypassphrase.\(conn.id.uuidString)"), !passphrase.isEmpty { + sshKeyPassphrase = passphrase } } } @@ -229,33 +237,32 @@ struct ConnectionFormView: View { } } .fileImporter( - isPresented: $showFilePicker, - allowedContentTypes: sqliteContentTypes, + isPresented: showFilePicker, + allowedContentTypes: activeFilePicker == .sqliteDatabase ? sqliteContentTypes : [.data], allowsMultipleSelection: false ) { result in - handleFilePickerResult(result) - } - .fileImporter( - isPresented: $showSSHKeyPicker, - allowedContentTypes: [.data], - allowsMultipleSelection: false - ) { result in - if case .success(let urls) = result, let url = urls.first { - guard url.startAccessingSecurityScopedResource() else { return } - defer { url.stopAccessingSecurityScopedResource() } - - // Read key content directly — more reliable than copying file - if let content = try? String(contentsOf: url, encoding: .utf8) { - sshKeyContent = content - sshKeyInputMode = .paste - } else { - // Fallback: copy to app Documents - guard let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } - let dest = docsDir.appendingPathComponent("ssh_" + url.lastPathComponent) - try? FileManager.default.removeItem(at: dest) - try? FileManager.default.copyItem(at: url, to: dest) - sshKeyPath = dest.path + let picker = activeFilePicker + activeFilePicker = nil + switch picker { + case .sqliteDatabase: + handleFilePickerResult(result) + case .sshKey: + if case .success(let urls) = result, let url = urls.first { + guard url.startAccessingSecurityScopedResource() else { return } + defer { url.stopAccessingSecurityScopedResource() } + if let content = try? String(contentsOf: url, encoding: .utf8) { + sshKeyContent = content + sshKeyInputMode = .paste + } else { + guard let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } + let dest = docsDir.appendingPathComponent("ssh_" + url.lastPathComponent) + try? FileManager.default.removeItem(at: dest) + try? FileManager.default.copyItem(at: url, to: dest) + sshKeyPath = dest.path + } } + case nil: + break } } .alert("New Database", isPresented: $showNewDatabaseAlert) { @@ -300,7 +307,7 @@ struct ConnectionFormView: View { } Button { - showFilePicker = true + activeFilePicker = .sqliteDatabase } label: { Label("Open Database File", systemImage: "folder") } @@ -377,7 +384,7 @@ struct ConnectionFormView: View { if sshKeyInputMode == .file { Button { - showSSHKeyPicker = true + activeFilePicker = .sshKey } label: { HStack { Text(sshKeyPath.isEmpty diff --git a/TableProMobile/TableProMobile/Views/GroupManagementView.swift b/TableProMobile/TableProMobile/Views/GroupManagementView.swift index d11d86147..89f17634a 100644 --- a/TableProMobile/TableProMobile/Views/GroupManagementView.swift +++ b/TableProMobile/TableProMobile/Views/GroupManagementView.swift @@ -46,11 +46,10 @@ struct GroupManagementView: View { .onMove { source, destination in var sorted = appState.groups.sorted(by: { $0.sortOrder < $1.sortOrder }) sorted.move(fromOffsets: source, toOffset: destination) - for (index, group) in sorted.enumerated() { - var updated = group - updated.sortOrder = index - appState.updateGroup(updated) + for index in sorted.indices { + sorted[index].sortOrder = index } + appState.reorderGroups(sorted) } } .overlay { @@ -65,7 +64,7 @@ struct GroupManagementView: View { .navigationTitle("Groups") .navigationBarTitleDisplayMode(.inline) .toolbar { - ToolbarItem(placement: .cancellationAction) { + ToolbarItem(placement: .topBarLeading) { Button("Done") { dismiss() } } ToolbarItemGroup(placement: .topBarTrailing) { diff --git a/TableProMobile/TableProMobile/Views/TagManagementView.swift b/TableProMobile/TableProMobile/Views/TagManagementView.swift index 7c2774893..07ad766dc 100644 --- a/TableProMobile/TableProMobile/Views/TagManagementView.swift +++ b/TableProMobile/TableProMobile/Views/TagManagementView.swift @@ -66,16 +66,16 @@ struct TagManagementView: View { .navigationTitle("Tags") .navigationBarTitleDisplayMode(.inline) .toolbar { - ToolbarItem(placement: .confirmationAction) { + ToolbarItem(placement: .topBarLeading) { + Button("Done") { dismiss() } + } + ToolbarItemGroup(placement: .topBarTrailing) { Button { showingAddTag = true } label: { Image(systemName: "plus") } } - ToolbarItem(placement: .cancellationAction) { - Button("Done") { dismiss() } - } } .sheet(isPresented: $showingAddTag) { TagFormSheet { tag in diff --git a/TableProMobile/TableProWidget/Views/MediumWidgetView.swift b/TableProMobile/TableProWidget/Views/MediumWidgetView.swift index ad37538d1..0949ed2d2 100644 --- a/TableProMobile/TableProWidget/Views/MediumWidgetView.swift +++ b/TableProMobile/TableProWidget/Views/MediumWidgetView.swift @@ -31,26 +31,28 @@ struct MediumWidgetView: View { } else { LazyVGrid(columns: columns, spacing: 8) { ForEach(displayedConnections) { connection in - Link(destination: URL(string: "tablepro://connect/\(connection.id.uuidString)")!) { - HStack(spacing: 8) { - Image(systemName: DatabaseTypeStyle.iconName(for: connection.type)) - .font(.callout) - .foregroundStyle(DatabaseTypeStyle.iconColor(for: connection.type)) - .frame(width: 28, height: 28) - .background(DatabaseTypeStyle.iconColor(for: connection.type).opacity(0.15)) - .clipShape(RoundedRectangle(cornerRadius: 6)) + if let url = URL(string: "tablepro://connect/\(connection.id.uuidString)") { + Link(destination: url) { + HStack(spacing: 8) { + Image(systemName: DatabaseTypeStyle.iconName(for: connection.type)) + .font(.callout) + .foregroundStyle(DatabaseTypeStyle.iconColor(for: connection.type)) + .frame(width: 28, height: 28) + .background(DatabaseTypeStyle.iconColor(for: connection.type).opacity(0.15)) + .clipShape(RoundedRectangle(cornerRadius: 6)) - Text(connection.name) - .font(.caption) - .foregroundStyle(.primary) - .lineLimit(1) + Text(connection.name) + .font(.caption) + .foregroundStyle(.primary) + .lineLimit(1) - Spacer(minLength: 0) + Spacer(minLength: 0) + } + .padding(.horizontal, 8) + .padding(.vertical, 6) + .background(.fill.quaternary) + .clipShape(RoundedRectangle(cornerRadius: 8)) } - .padding(.horizontal, 8) - .padding(.vertical, 6) - .background(.fill.quaternary) - .clipShape(RoundedRectangle(cornerRadius: 8)) } } } diff --git a/TableProMobile/TableProWidgetExtension.entitlements b/TableProMobile/TableProWidgetExtension.entitlements index 2eb7e333a..930e4ce9b 100644 --- a/TableProMobile/TableProWidgetExtension.entitlements +++ b/TableProMobile/TableProWidgetExtension.entitlements @@ -3,6 +3,8 @@ com.apple.security.application-groups - + + group.com.TablePro.TableProMobile +