diff --git a/CHANGELOG.md b/CHANGELOG.md index df9d4c6ed..1cf5f6826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Export query results directly to CSV, JSON, SQL, XLSX, or MQL via File menu, context menu, or toolbar - Pro license gating for Safe Mode (Touch ID) and XLSX export - License activation dialog diff --git a/TablePro/Core/Plugins/QueryResultExportDataSource.swift b/TablePro/Core/Plugins/QueryResultExportDataSource.swift new file mode 100644 index 000000000..3ef8b7dc9 --- /dev/null +++ b/TablePro/Core/Plugins/QueryResultExportDataSource.swift @@ -0,0 +1,80 @@ +// +// QueryResultExportDataSource.swift +// TablePro +// + +import Foundation +import os +import TableProPluginKit + +/// In-memory `PluginExportDataSource` backed by a RowBuffer snapshot. +/// Allows export plugins (CSV, JSON, SQL, XLSX, MQL) to export query results +/// without modification to the plugins themselves. +final class QueryResultExportDataSource: PluginExportDataSource, @unchecked Sendable { + let databaseTypeId: String + + private let columns: [String] + private let columnTypeNames: [String] + private let rows: [[String?]] + private let driver: DatabaseDriver? + + private static let logger = Logger(subsystem: "com.TablePro", category: "QueryResultExportDataSource") + + init(rowBuffer: RowBuffer, databaseType: DatabaseType, driver: DatabaseDriver?) { + self.databaseTypeId = databaseType.rawValue + self.driver = driver + + // Snapshot data at init time for thread safety + self.columns = rowBuffer.columns + self.columnTypeNames = rowBuffer.columnTypes.map { $0.rawType ?? "" } + self.rows = rowBuffer.rows.map { $0.values } + } + + func fetchRows(table: String, databaseName: String, offset: Int, limit: Int) async throws -> PluginQueryResult { + let start = min(offset, rows.count) + let end = min(start + limit, rows.count) + let slice = Array(rows[start ..< end]) + + return PluginQueryResult( + columns: columns, + columnTypeNames: columnTypeNames, + rows: slice, + rowsAffected: 0, + executionTime: 0 + ) + } + + func fetchApproximateRowCount(table: String, databaseName: String) async throws -> Int? { + rows.count + } + + func quoteIdentifier(_ identifier: String) -> String { + if let driver { + return driver.quoteIdentifier(identifier) + } + return "\"\(identifier.replacingOccurrences(of: "\"", with: "\"\""))\"" + } + + func escapeStringLiteral(_ value: String) -> String { + if let driver { + return driver.escapeStringLiteral(value) + } + return value.replacingOccurrences(of: "'", with: "''") + } + + func fetchTableDDL(table: String, databaseName: String) async throws -> String { + "" + } + + func execute(query: String) async throws -> PluginQueryResult { + throw ExportError.exportFailed("Execute is not supported for in-memory query result export") + } + + func fetchDependentSequences(table: String, databaseName: String) async throws -> [PluginSequenceInfo] { + [] + } + + func fetchDependentTypes(table: String, databaseName: String) async throws -> [PluginEnumTypeInfo] { + [] + } +} diff --git a/TablePro/Core/Services/Export/ExportService.swift b/TablePro/Core/Services/Export/ExportService.swift index 898ec470d..c16fc76e0 100644 --- a/TablePro/Core/Services/Export/ExportService.swift +++ b/TablePro/Core/Services/Export/ExportService.swift @@ -62,7 +62,7 @@ final class ExportService { var state = ExportState() - private let driver: DatabaseDriver + private let driver: DatabaseDriver? private let databaseType: DatabaseType init(driver: DatabaseDriver, databaseType: DatabaseType) { @@ -70,6 +70,12 @@ final class ExportService { self.databaseType = databaseType } + /// Convenience initializer for query results export (no driver needed). + init(databaseType: DatabaseType) { + self.driver = nil + self.databaseType = databaseType + } + // MARK: - Cancellation private let isCancelledLock = NSLock() @@ -120,8 +126,12 @@ final class ExportService { currentProgress = nil } + guard let driver else { + throw ExportError.notConnected + } + // Fetch total row counts - state.totalRows = await fetchTotalRowCount(for: tables) + state.totalRows = await fetchTotalRowCount(for: tables, driver: driver) // Create data source adapter let dataSource = ExportDataSourceAdapter(driver: driver, databaseType: databaseType) @@ -183,9 +193,89 @@ final class ExportService { state.progress = 1.0 } + // MARK: - Query Results Export + + func exportQueryResults( + rowBuffer: RowBuffer, + config: ExportConfiguration, + to url: URL + ) async throws { + guard let plugin = PluginManager.shared.exportPlugins[config.formatId] else { + throw ExportError.formatNotFound(config.formatId) + } + + let totalRows = rowBuffer.rows.count + state = ExportState(isExporting: true, totalTables: 1, totalRows: totalRows) + isCancelled = false + + defer { + state.isExporting = false + isCancelled = false + state.statusMessage = "" + currentProgress = nil + } + + let dataSource = QueryResultExportDataSource( + rowBuffer: rowBuffer, + databaseType: databaseType, + driver: driver + ) + + let progress = PluginExportProgress() + currentProgress = progress + progress.setTotalRows(totalRows) + + let pendingUpdate = ProgressUpdateCoalescer() + progress.onUpdate = { [weak self] table, index, rows, total, status in + let shouldDispatch = pendingUpdate.markPending() + if shouldDispatch { + Task { @MainActor [weak self] in + pendingUpdate.clearPending() + guard let self else { return } + self.state.currentTable = table + self.state.currentTableIndex = index + self.state.processedRows = rows + if total > 0 { + self.state.progress = Double(rows) / Double(total) + } + if !status.isEmpty { + self.state.statusMessage = status + } + } + } + } + + let exportTable = PluginExportTable( + name: config.fileName, + databaseName: "", + tableType: "query", + optionValues: plugin.defaultTableOptionValues() + ) + + do { + try await plugin.export( + tables: [exportTable], + dataSource: dataSource, + destination: url, + progress: progress + ) + } catch { + try? FileManager.default.removeItem(at: url) + state.errorMessage = error.localizedDescription + throw error + } + + let pluginWarnings = plugin.warnings + if !pluginWarnings.isEmpty { + state.warningMessage = pluginWarnings.joined(separator: "\n") + } + + state.progress = 1.0 + } + // MARK: - Row Count Fetching - private func fetchTotalRowCount(for tables: [ExportTableItem]) async -> Int { + private func fetchTotalRowCount(for tables: [ExportTableItem], driver: DatabaseDriver) async -> Int { guard !tables.isEmpty else { return 0 } var total = 0 diff --git a/TablePro/Core/Services/Infrastructure/AppNotifications.swift b/TablePro/Core/Services/Infrastructure/AppNotifications.swift index 65d1c8339..b72cdbd11 100644 --- a/TablePro/Core/Services/Infrastructure/AppNotifications.swift +++ b/TablePro/Core/Services/Infrastructure/AppNotifications.swift @@ -24,6 +24,10 @@ extension Notification.Name { static let licenseStatusDidChange = Notification.Name("licenseStatusDidChange") + // MARK: - Export + + static let exportQueryResults = Notification.Name("exportQueryResults") + // MARK: - SQL Favorites static let sqlFavoritesDidUpdate = Notification.Name("sqlFavoritesDidUpdate") diff --git a/TablePro/Models/Export/ExportModels.swift b/TablePro/Models/Export/ExportModels.swift index eeca5bc26..1e8b6b46a 100644 --- a/TablePro/Models/Export/ExportModels.swift +++ b/TablePro/Models/Export/ExportModels.swift @@ -6,6 +6,14 @@ import Foundation import TableProPluginKit +// MARK: - Export Mode + +/// Defines the export mode: either exporting database tables or in-memory query results. +enum ExportMode { + case tables(connection: DatabaseConnection, preselectedTables: Set) + case queryResults(connection: DatabaseConnection, rowBuffer: RowBuffer, suggestedFileName: String) +} + // MARK: - Export Configuration @MainActor diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index bc91e3198..662ae8c76 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -142,16 +142,16 @@ "value": "\"%@\" bu Mac ve başka bir cihazda değiştirildi." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "\"%@\" 在此 Mac 和另一台设备上均已修改。" + "value": "\"%@\" đã bị thay đổi trên cả máy Mac này và thiết bị khác." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "\"%@\" đã bị thay đổi trên cả máy Mac này và thiết bị khác." + "value": "\"%@\" 在此 Mac 和另一台设备上均已修改。" } } } @@ -330,16 +330,16 @@ "value": "(bu Mac)" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "(此 Mac)" + "value": "(máy Mac này)" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "(máy Mac này)" + "value": "(此 Mac)" } } } @@ -425,16 +425,16 @@ "value": "%@ (%@@%@)" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "%@(%@@%@)" + "value": "%@ (%@@%@)" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "%@ (%@@%@)" + "value": "%@(%@@%@)" } } } @@ -475,16 +475,16 @@ "value": "%@ (Pro)" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "%@(Pro)" + "value": "%@ (Pro)" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "%@ (Pro)" + "value": "%@(Pro)" } } } @@ -741,16 +741,16 @@ "value": "%@ requires a Pro license" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "%@ 需要 Pro 许可证" + "value": "%@ yêu cầu giấy phép Pro" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "%@ yêu cầu giấy phép Pro" + "value": "%@ 需要 Pro 许可证" } } } @@ -1002,16 +1002,16 @@ "value": "%lld bayt" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "%lld 字节" + "value": "%lld byte" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "%lld byte" + "value": "%lld 字节" } } } @@ -1030,16 +1030,16 @@ "value": "%lld bağlantı bu profili kullanıyor. SSH tüneli olmadan bağlanmaya geri dönecekler." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "%lld 个连接正在使用此配置。它们将回退为不使用 SSH 隧道。" + "value": "%lld kết nối đang sử dụng hồ sơ này. Chúng sẽ chuyển về không dùng SSH tunnel." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "%lld kết nối đang sử dụng hồ sơ này. Chúng sẽ chuyển về không dùng SSH tunnel." + "value": "%lld 个连接正在使用此配置。它们将回退为不使用 SSH 隧道。" } } } @@ -1207,6 +1207,34 @@ } } }, + "%lld row%@ to export": { + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "%1$lld row%2$@ to export" + } + }, + "vi": { + "stringUnit": { + "state": "translated", + "value": "%lld hàng%@ để export" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%lld 行%@待导出" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%lld satır%@ dışa aktarılacak" + } + } + } + }, "%lld seconds": { "localizations": { "tr": { @@ -1819,16 +1847,16 @@ "value": "1 / %lld çakışma" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "第 1 个冲突,共 %lld 个" + "value": "1 trong %lld xung đột" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "1 trong %lld xung đột" + "value": "第 1 个冲突,共 %lld 个" } } } @@ -2415,16 +2443,16 @@ "value": "Bir eşitleme çakışması tespit edildi ve çözülmesi gerekiyor." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "检测到同步冲突,需要解决。" + "value": "Phát hiện xung đột đồng bộ và cần được giải quyết." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Phát hiện xung đột đồng bộ và cần được giải quyết." + "value": "检测到同步冲突,需要解决。" } } } @@ -2488,13 +2516,13 @@ "value": "Access Key ID" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "Access Key ID" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "Access Key ID" @@ -2516,16 +2544,16 @@ "value": "Account ID" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "账户 ID" + "value": "Account ID" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Account ID" + "value": "账户 ID" } } } @@ -2538,16 +2566,16 @@ "value": "Hesap:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "账户:" + "value": "Tài khoản:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tài khoản:" + "value": "账户:" } } } @@ -2582,16 +2610,16 @@ "value": "Lisansı Etkinleştir" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "激活许可证" + "value": "Kích hoạt giấy phép" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kích hoạt giấy phép" + "value": "激活许可证" } } } @@ -2604,16 +2632,16 @@ "value": "Lisansı Etkinleştir..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "激活许可证…" + "value": "Kích hoạt giấy phép..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kích hoạt giấy phép..." + "value": "激活许可证…" } } } @@ -2654,16 +2682,16 @@ "value": "Etkinleştirmeler (%lld / %lld)" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "激活数(%lld / %lld)" + "value": "Kích hoạt (%1$lld / %2$lld)" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kích hoạt (%1$lld / %2$lld)" + "value": "激活数(%lld / %lld)" } } } @@ -2742,16 +2770,16 @@ "value": "Ekle" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "添加" + "value": "Thêm" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thêm" + "value": "添加" } } } @@ -3515,16 +3543,16 @@ "value": "Alternatif Satır" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "交替行" + "value": "Dòng xen kẽ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Dòng xen kẽ" + "value": "交替行" } } } @@ -3582,16 +3610,16 @@ "value": "Bilinmeyen bir eşitleme hatası oluştu: %@" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "发生未知同步错误:%@" + "value": "Đã xảy ra lỗi đồng bộ không xác định: %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã xảy ra lỗi đồng bộ không xác định: %@" + "value": "发生未知同步错误:%@" } } } @@ -3648,16 +3676,16 @@ "value": "Animasyonlar" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "动画" + "value": "Hiệu ứng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hiệu ứng" + "value": "动画" } } } @@ -3698,13 +3726,13 @@ "value": "API Token" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "API Token" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "API Token" @@ -4080,16 +4108,16 @@ "value": "Kimlik Doğrulama Yöntemi" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "认证方式" + "value": "Phương thức xác thực" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Phương thức xác thực" + "value": "认证方式" } } } @@ -4256,16 +4284,16 @@ "value": "Otomatik" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "自动" + "value": "Tự động" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tự động" + "value": "自动" } } } @@ -4484,16 +4512,16 @@ "value": "AWS Region" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "AWS 区域" + "value": "AWS Region" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "AWS Region" + "value": "AWS 区域" } } } @@ -4506,16 +4534,16 @@ "value": "Arka Plan" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "背景" + "value": "Nền" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nền" + "value": "背景" } } } @@ -4528,16 +4556,16 @@ "value": "Rozet Arka Planı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "徽章背景" + "value": "Nền huy hiệu" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nền huy hiệu" + "value": "徽章背景" } } } @@ -4550,16 +4578,16 @@ "value": "Rozetler" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "徽章" + "value": "Huy hiệu" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Huy hiệu" + "value": "徽章" } } } @@ -4644,16 +4672,16 @@ "value": "Faturalandırma:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "账单:" + "value": "Thanh toán:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thanh toán:" + "value": "账单:" } } } @@ -4688,16 +4716,16 @@ "value": "Gövde" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "正文" + "value": "Nội dung" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nội dung" + "value": "正文" } } } @@ -4710,16 +4738,16 @@ "value": "Bool False" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "布尔值 False" + "value": "Bool False" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bool False" + "value": "布尔值 False" } } } @@ -4732,16 +4760,16 @@ "value": "Bool True" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "布尔值 True" + "value": "Bool True" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bool True" + "value": "布尔值 True" } } } @@ -4754,16 +4782,16 @@ "value": "Kenarlık" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "边框" + "value": "Viền" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Viền" + "value": "边框" } } } @@ -5064,16 +5092,16 @@ "value": "Başlık" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "标题说明" + "value": "Chú thích" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chú thích" + "value": "标题说明" } } } @@ -5086,16 +5114,16 @@ "value": "Kart Arka Planı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "卡片背景" + "value": "Nền thẻ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nền thẻ" + "value": "卡片背景" } } } @@ -5359,16 +5387,16 @@ "value": "Durumu Kontrol Et" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "检查状态" + "value": "Kiểm tra trạng thái" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kiểm tra trạng thái" + "value": "检查状态" } } } @@ -5849,16 +5877,16 @@ "value": "İstemci Sertifikası" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "客户端证书" + "value": "Chứng chỉ máy khách" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chứng chỉ máy khách" + "value": "客户端证书" } } } @@ -6116,16 +6144,16 @@ "value": "Renkler" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "颜色" + "value": "Màu sắc" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Màu sắc" + "value": "颜色" } } } @@ -6530,16 +6558,16 @@ "value": "Lisansınızı doğrulamak için internete bağlanın." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "请连接互联网以验证您的许可证。" + "value": "Kết nối internet để xác minh giấy phép của bạn." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kết nối internet để xác minh giấy phép của bạn." + "value": "请连接互联网以验证您的许可证。" } } } @@ -6906,16 +6934,16 @@ "value": "Bağlantılar:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "连接:" + "value": "Kết nối:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kết nối:" + "value": "连接:" } } } @@ -7017,16 +7045,16 @@ "value": "Kontrol Arka Planı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "控件背景" + "value": "Nền điều khiển" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nền điều khiển" + "value": "控件背景" } } } @@ -7241,16 +7269,16 @@ "value": "Hex Olarak Kopyala" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "复制为十六进制" + "value": "Sao chép dạng Hex" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Sao chép dạng Hex" + "value": "复制为十六进制" } } } @@ -7263,16 +7291,16 @@ "value": "JSON Olarak Kopyala" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "复制为 JSON" + "value": "Sao chép dạng JSON" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Sao chép dạng JSON" + "value": "复制为 JSON" } } } @@ -7462,16 +7490,16 @@ "value": "Köşe Yarıçapı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "圆角半径" + "value": "Bo góc" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bo góc" + "value": "圆角半径" } } } @@ -7506,16 +7534,16 @@ "value": "Lisans sunucusuna ulaşılamadı. İnternet bağlantınızı kontrol edip tekrar deneyin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "无法连接许可证服务器。请检查网络连接后重试。" + "value": "Không thể kết nối đến máy chủ giấy phép. Kiểm tra kết nối internet và thử lại." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không thể kết nối đến máy chủ giấy phép. Kiểm tra kết nối internet và thử lại." + "value": "无法连接许可证服务器。请检查网络连接后重试。" } } } @@ -7528,16 +7556,16 @@ "value": "Sunucu yanıtı okunamadı. Biraz sonra tekrar deneyin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "无法读取服务器响应。请稍后重试。" + "value": "Không thể đọc phản hồi từ máy chủ. Vui lòng thử lại sau." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không thể đọc phản hồi từ máy chủ. Vui lòng thử lại sau." + "value": "无法读取服务器响应。请稍后重试。" } } } @@ -7776,16 +7804,16 @@ "value": "Yeni Profil Oluştur..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "新建配置…" + "value": "Tạo hồ sơ mới..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tạo hồ sơ mới..." + "value": "新建配置…" } } } @@ -8019,16 +8047,16 @@ "value": "Mevcut Satır" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "当前行" + "value": "Dòng hiện tại" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Dòng hiện tại" + "value": "当前行" } } } @@ -8109,16 +8137,16 @@ "value": "İmleç" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "光标" + "value": "Con trỏ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Con trỏ" + "value": "光标" } } } @@ -8209,16 +8237,16 @@ "value": "Özel Uç Nokta" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "自定义端点" + "value": "Endpoint tùy chỉnh" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Endpoint tùy chỉnh" + "value": "自定义端点" } } } @@ -8363,16 +8391,16 @@ "value": "Veri Tablosu Yazı Tipi" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "数据表格字体" + "value": "Phông chữ bảng dữ liệu" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Phông chữ bảng dữ liệu" + "value": "数据表格字体" } } } @@ -8540,16 +8568,16 @@ "value": "Veritabanı İndeksi" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "数据库索引" + "value": "Chỉ mục cơ sở dữ liệu" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chỉ mục cơ sở dữ liệu" + "value": "数据库索引" } } } @@ -9344,16 +9372,16 @@ "value": "Klasörü Sil" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "删除文件夹" + "value": "Xoá thư mục" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xoá thư mục" + "value": "删除文件夹" } } } @@ -9366,16 +9394,16 @@ "value": "Klasör Silinsin mi?" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "删除文件夹?" + "value": "Xoá thư mục?" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xoá thư mục?" + "value": "删除文件夹?" } } } @@ -9484,16 +9512,16 @@ "value": "Profili Sil" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "删除配置" + "value": "Xoá hồ sơ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xoá hồ sơ" + "value": "删除配置" } } } @@ -9512,16 +9540,16 @@ "value": "SSH Profili Silinsin mi?" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "删除 SSH 配置?" + "value": "Xoá hồ sơ SSH?" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xoá hồ sơ SSH?" + "value": "删除 SSH 配置?" } } } @@ -9534,16 +9562,16 @@ "value": "Temayı Sil" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "删除主题" + "value": "Xoá chủ đề" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xoá chủ đề" + "value": "删除主题" } } } @@ -9556,16 +9584,16 @@ "value": "Silindi" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "已删除" + "value": "Đã xoá" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã xoá" + "value": "已删除" } } } @@ -9578,16 +9606,16 @@ "value": "Silinen Metin" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "已删除文本" + "value": "Văn bản đã xoá" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Văn bản đã xoá" + "value": "已删除文本" } } } @@ -10198,16 +10226,16 @@ "value": "Renk ve düzeni özelleştirmek için çoğaltın." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "复制后可自定义颜色和布局。" + "value": "Nhân bản để tuỳ chỉnh màu sắc và bố cục." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nhân bản để tuỳ chỉnh màu sắc và bố cục." + "value": "复制后可自定义颜色和布局。" } } } @@ -10265,16 +10293,16 @@ "value": "Temayı Çoğalt" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "复制主题" + "value": "Nhân bản chủ đề" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nhân bản chủ đề" + "value": "复制主题" } } } @@ -10382,16 +10410,16 @@ "value": "Profili Düzenle..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "编辑配置…" + "value": "Chỉnh sửa hồ sơ..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chỉnh sửa hồ sơ..." + "value": "编辑配置…" } } } @@ -10471,16 +10499,16 @@ "value": "Düzenle..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "编辑…" + "value": "Chỉnh sửa..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chỉnh sửa..." + "value": "编辑…" } } } @@ -10537,16 +10565,16 @@ "value": "Düzenleyici Yazı Tipi" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "编辑器字体" + "value": "Phông chữ trình soạn thảo" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Phông chữ trình soạn thảo" + "value": "编辑器字体" } } } @@ -10626,16 +10654,16 @@ "value": "%@ Etkinleştir" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "启用 %@" + "value": "Bật %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bật %@" + "value": "启用 %@" } } } @@ -10935,16 +10963,16 @@ "value": "Pro özelliklerin kilidini açmak için lisans anahtarınızı girin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "输入许可证密钥以解锁 Pro 功能。" + "value": "Nhập mã giấy phép để mở khoá các tính năng Pro." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nhập mã giấy phép để mở khoá các tính năng Pro." + "value": "输入许可证密钥以解锁 Pro 功能。" } } } @@ -11266,16 +11294,16 @@ "value": "Çalıştırıldı: %@" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "已执行:%@" + "value": "Đã thực thi: %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã thực thi: %@" + "value": "已执行:%@" } } } @@ -11360,16 +11388,16 @@ "value": "Bitiş Tarihi:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "到期时间:" + "value": "Hết hạn:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hết hạn:" + "value": "到期时间:" } } } @@ -11529,6 +11557,62 @@ } } }, + "Export %lld row(s) to %@": { + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "Export %1$lld row(s) to %2$@" + } + }, + "vi": { + "stringUnit": { + "state": "translated", + "value": "Export %lld hàng sang %@" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "导出 %lld 行到 %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%lld satırı %@ olarak dışa aktar" + } + } + } + }, + "Export %lld table(s) to %@": { + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "Export %1$lld table(s) to %2$@" + } + }, + "vi": { + "stringUnit": { + "state": "translated", + "value": "Export %lld bảng sang %@" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "导出 %lld 张表到 %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%lld tabloyu %@ olarak dışa aktar" + } + } + } + }, "Export completed successfully": { "localizations": { "tr": { @@ -11759,16 +11843,38 @@ "value": "Sorgu sonuçlarını ve tabloları Excel biçiminde dışa aktarın." } }, + "vi": { + "stringUnit": { + "state": "translated", + "value": "Xuất kết quả truy vấn và bảng sang định dạng Excel." + } + }, "zh-Hans": { "stringUnit": { "state": "translated", "value": "将查询结果和表导出为 Excel 格式。" } - }, + } + } + }, + "Export Results...": { + "localizations": { "vi": { "stringUnit": { "state": "translated", - "value": "Xuất kết quả truy vấn và bảng sang định dạng Excel." + "value": "Export Kết Quả..." + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "导出结果..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonuçları Dışa Aktar..." } } } @@ -11871,16 +11977,16 @@ "value": "Çok Büyük" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "特大" + "value": "Rất lớn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Rất lớn" + "value": "特大" } } } @@ -12026,16 +12132,16 @@ "value": "Eşitleme verisi kodlanamadı: %@" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "同步数据编码失败:%@" + "value": "Không thể mã hoá dữ liệu đồng bộ: %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không thể mã hoá dữ liệu đồng bộ: %@" + "value": "同步数据编码失败:%@" } } } @@ -12385,16 +12491,16 @@ "value": "Sunucu yanıtı ayrıştırılamadı: %@" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "服务器响应解析失败:%@" + "value": "Không thể phân tích phản hồi máy chủ: %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không thể phân tích phản hồi máy chủ: %@" + "value": "服务器响应解析失败:%@" } } } @@ -12570,16 +12676,16 @@ "value": "Aile" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "字体族" + "value": "Họ phông" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Họ phông" + "value": "字体族" } } } @@ -12592,16 +12698,16 @@ "value": "Hızlı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "快速" + "value": "Nhanh" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nhanh" + "value": "快速" } } } @@ -12614,16 +12720,16 @@ "value": "Sık Kullanılanlar" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "收藏" + "value": "Yêu thích" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Yêu thích" + "value": "收藏" } } } @@ -12836,16 +12942,16 @@ "value": "Filtre sık kullanılanları" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "筛选收藏" + "value": "Lọc yêu thích" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lọc yêu thích" + "value": "筛选收藏" } } } @@ -13056,16 +13162,16 @@ "value": "Odak Kenarlığı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "焦点边框" + "value": "Viền khi chọn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Viền khi chọn" + "value": "焦点边框" } } } @@ -13078,16 +13184,16 @@ "value": "Klasör adı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "文件夹名称" + "value": "Tên thư mục" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tên thư mục" + "value": "文件夹名称" } } } @@ -13146,16 +13252,16 @@ "value": "Yazı Tipleri" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "字体" + "value": "Phông chữ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Phông chữ" + "value": "字体" } } } @@ -13278,16 +13384,16 @@ "value": "Fonksiyon" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "函数" + "value": "Hàm" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hàm" + "value": "函数" } } } @@ -13410,16 +13516,16 @@ "value": "GitHub Deposu" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "GitHub 仓库" + "value": "Kho GitHub" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kho GitHub" + "value": "GitHub 仓库" } } } @@ -13432,16 +13538,16 @@ "value": "Genel:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "全局:" + "value": "Chung:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chung:" + "value": "全局:" } } } @@ -13653,16 +13759,16 @@ "value": "Gruplar ve Etiketler:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "分组与标签:" + "value": "Nhóm & thẻ:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nhóm & thẻ:" + "value": "分组与标签:" } } } @@ -13697,16 +13803,16 @@ "value": "Hex bayt" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "十六进制字节" + "value": "Byte dạng Hex" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Byte dạng Hex" + "value": "十六进制字节" } } } @@ -13874,16 +13980,16 @@ "value": "Geçmiş Limiti:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "历史记录上限:" + "value": "Giới hạn lịch sử:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giới hạn lịch sử:" + "value": "历史记录上限:" } } } @@ -13940,16 +14046,16 @@ "value": "Üzerine Gelme" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "悬停" + "value": "Di chuột" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Di chuột" + "value": "悬停" } } } @@ -13962,16 +14068,16 @@ "value": "Çok Büyük" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "超大" + "value": "Rất lớn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Rất lớn" + "value": "超大" } } } @@ -13984,16 +14090,16 @@ "value": "iCloud account is not available. Sign in to iCloud in System Settings." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 账户不可用。请在系统设置中登录 iCloud。" + "value": "Tài khoản iCloud không khả dụng. Đăng nhập iCloud trong Cài đặt hệ thống." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tài khoản iCloud không khả dụng. Đăng nhập iCloud trong Cài đặt hệ thống." + "value": "iCloud 账户不可用。请在系统设置中登录 iCloud。" } } } @@ -14006,16 +14112,16 @@ "value": "iCloud Connected" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 已连接" + "value": "Đã kết nối iCloud" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã kết nối iCloud" + "value": "iCloud 已连接" } } } @@ -14028,16 +14134,16 @@ "value": "iCloud server error: %@" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 服务器错误:%@" + "value": "Lỗi máy chủ iCloud: %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lỗi máy chủ iCloud: %@" + "value": "iCloud 服务器错误:%@" } } } @@ -14050,16 +14156,16 @@ "value": "iCloud storage is full. Free up space or reduce the history sync limit." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 储存空间已满。请释放空间或减少历史记录同步上限。" + "value": "Bộ nhớ iCloud đã đầy. Giải phóng dung lượng hoặc giảm giới hạn đồng bộ lịch sử." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bộ nhớ iCloud đã đầy. Giải phóng dung lượng hoặc giảm giới hạn đồng bộ lịch sử." + "value": "iCloud 储存空间已满。请释放空间或减少历史记录同步上限。" } } } @@ -14072,16 +14178,16 @@ "value": "iCloud Sync" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 同步" + "value": "Đồng bộ iCloud" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ iCloud" + "value": "iCloud 同步" } } } @@ -14094,16 +14200,16 @@ "value": "iCloud Sync is active" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 同步已启用" + "value": "Đồng bộ iCloud đang hoạt động" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ iCloud đang hoạt động" + "value": "iCloud 同步已启用" } } } @@ -14116,16 +14222,16 @@ "value": "iCloud Sync:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 同步:" + "value": "Đồng bộ iCloud:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ iCloud:" + "value": "iCloud 同步:" } } } @@ -14138,16 +14244,16 @@ "value": "Simge Boyutları" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "图标大小" + "value": "Kích thước biểu tượng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kích thước biểu tượng" + "value": "图标大小" } } } @@ -14771,16 +14877,16 @@ "value": "Bilgi" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "信息" + "value": "Thông tin" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thông tin" + "value": "信息" } } } @@ -14799,16 +14905,16 @@ "value": "Satır İçi Yapılandırma" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "内联配置" + "value": "Cấu hình nội tuyến" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cấu hình nội tuyến" + "value": "内联配置" } } } @@ -14865,16 +14971,16 @@ "value": "Düzenleyiciye Ekle" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "插入到编辑器" + "value": "Chèn vào trình soạn thảo" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chèn vào trình soạn thảo" + "value": "插入到编辑器" } } } @@ -14909,16 +15015,16 @@ "value": "Eklendi" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "已插入" + "value": "Đã chèn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã chèn" + "value": "已插入" } } } @@ -15042,16 +15148,16 @@ "value": "Temayı Yükle" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "安装主题" + "value": "Cài đặt chủ đề" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cài đặt chủ đề" + "value": "安装主题" } } } @@ -15153,16 +15259,16 @@ "value": "Arayüz" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "界面" + "value": "Giao diện" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giao diện" + "value": "界面" } } } @@ -15287,16 +15393,16 @@ "value": "Geçersiz hex" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "无效的十六进制" + "value": "Hex không hợp lệ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hex không hợp lệ" + "value": "无效的十六进制" } } } @@ -15354,16 +15460,16 @@ "value": "Geçersiz lisans anahtarı biçimi. Yazım hatalarını kontrol edip tekrar deneyin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证密钥格式无效。请检查是否有拼写错误后重试。" + "value": "Định dạng mã giấy phép không hợp lệ. Kiểm tra lỗi chính tả và thử lại." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Định dạng mã giấy phép không hợp lệ. Kiểm tra lỗi chính tả và thử lại." + "value": "许可证密钥格式无效。请检查是否有拼写错误后重试。" } } } @@ -15443,16 +15549,16 @@ "value": "Görünmezler" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "不可见字符" + "value": "Ký tự ẩn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Ký tự ẩn" + "value": "不可见字符" } } } @@ -15619,13 +15725,13 @@ "value": "JSON" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "JSON" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "JSON" @@ -15730,16 +15836,16 @@ "value": "Keep Other Version" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "保留其他版本" + "value": "Giữ phiên bản khác" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giữ phiên bản khác" + "value": "保留其他版本" } } } @@ -15752,16 +15858,16 @@ "value": "Keep This Mac's Version" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "保留此 Mac 的版本" + "value": "Giữ phiên bản máy Mac này" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giữ phiên bản máy Mac này" + "value": "保留此 Mac 的版本" } } } @@ -15796,16 +15902,16 @@ "value": "Anahtar Önek Kökü" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "键前缀根" + "value": "Tiền tố gốc" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tiền tố gốc" + "value": "键前缀根" } } } @@ -15884,16 +15990,16 @@ "value": "Anahtar Kelime" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "关键字" + "value": "Từ khoá" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Từ khoá" + "value": "关键字" } } } @@ -15906,16 +16012,16 @@ "value": "Anahtar kelime boşluk içeremez" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "关键字不能包含空格" + "value": "Từ khoá không được chứa khoảng trắng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Từ khoá không được chứa khoảng trắng" + "value": "关键字不能包含空格" } } } @@ -15928,16 +16034,16 @@ "value": "Anahtar Kelime:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "关键字:" + "value": "Từ khoá:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Từ khoá:" + "value": "关键字:" } } } @@ -15950,16 +16056,16 @@ "value": "keyword: %@" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "关键字:%@" + "value": "từ khoá: %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "từ khoá: %@" + "value": "关键字:%@" } } } @@ -15994,16 +16100,16 @@ "value": "Büyük" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "大" + "value": "Lớn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lớn" + "value": "大" } } } @@ -16104,16 +16210,16 @@ "value": "Son eşitleme %@" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "上次同步 %@" + "value": "Đồng bộ lần cuối %@" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ lần cuối %@" + "value": "上次同步 %@" } } } @@ -16126,16 +16232,16 @@ "value": "Son Eşitlendi:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "上次同步:" + "value": "Đồng bộ lần cuối:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ lần cuối:" + "value": "上次同步:" } } } @@ -16170,16 +16276,16 @@ "value": "Düzen" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "布局" + "value": "Bố cục" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bố cục" + "value": "布局" } } } @@ -16304,16 +16410,16 @@ "value": "Lisans süresi doldu — eşitleme duraklatıldı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证已过期 - 同步已暂停" + "value": "Giấy phép hết hạn — tạm dừng đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép hết hạn — tạm dừng đồng bộ" + "value": "许可证已过期 - 同步已暂停" } } } @@ -16332,16 +16438,16 @@ "value": "Lisans %lld gün içinde sona eriyor" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证将在 %lld 天后过期" + "value": "Giấy phép hết hạn sau %lld ngày" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép hết hạn sau %lld ngày" + "value": "许可证将在 %lld 天后过期" } } } @@ -16376,16 +16482,16 @@ "value": "Lisans açık anahtarı geçersiz." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证公钥无效。" + "value": "Khoá công khai giấy phép không hợp lệ." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Khoá công khai giấy phép không hợp lệ." + "value": "许可证公钥无效。" } } } @@ -16398,16 +16504,16 @@ "value": "Uygulama paketinde lisans açık anahtarı bulunamadı." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "应用包中未找到许可证公钥。" + "value": "Không tìm thấy khoá công khai giấy phép trong gói ứng dụng." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không tìm thấy khoá công khai giấy phép trong gói ứng dụng." + "value": "应用包中未找到许可证公钥。" } } } @@ -16420,16 +16526,16 @@ "value": "Lisans imza doğrulaması başarısız oldu." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证签名验证失败。" + "value": "Xác minh chữ ký giấy phép thất bại." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xác minh chữ ký giấy phép thất bại." + "value": "许可证签名验证失败。" } } } @@ -16448,16 +16554,16 @@ "value": "Lisans doğrulaması başarısız oldu" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证验证失败" + "value": "Xác thực giấy phép thất bại" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xác thực giấy phép thất bại" + "value": "许可证验证失败" } } } @@ -16470,16 +16576,16 @@ "value": "Lisans doğrulaması başarısız oldu. Uygulamayı en son sürüme güncellemeyi deneyin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证验证失败。请尝试更新到最新版本。" + "value": "Xác minh giấy phép thất bại. Hãy thử cập nhật ứng dụng lên phiên bản mới nhất." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xác minh giấy phép thất bại. Hãy thử cập nhật ứng dụng lên phiên bản mới nhất." + "value": "许可证验证失败。请尝试更新到最新版本。" } } } @@ -16498,16 +16604,16 @@ "value": "Ömür Boyu" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "终身" + "value": "Trọn đời" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Trọn đời" + "value": "终身" } } } @@ -16587,16 +16693,16 @@ "value": "Satır Numarası" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "行号" + "value": "Số dòng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Số dòng" + "value": "行号" } } } @@ -16945,16 +17051,16 @@ "value": "Çok Büyük" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "巨大" + "value": "Cực lớn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cực lớn" + "value": "巨大" } } } @@ -17121,16 +17227,16 @@ "value": "Maksimum etkinleştirme sayısına ulaşıldı." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "已达到最大激活数。" + "value": "Đã đạt số lượng kích hoạt tối đa." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã đạt số lượng kích hoạt tối đa." + "value": "已达到最大激活数。" } } } @@ -17165,16 +17271,16 @@ "value": "Orta" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "中等" + "value": "Trung bình" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Trung bình" + "value": "中等" } } } @@ -17320,16 +17426,16 @@ "value": "Değiştirildi:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "修改时间:" + "value": "Đã sửa đổi:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã sửa đổi:" + "value": "修改时间:" } } } @@ -17411,16 +17517,16 @@ "value": "Taşı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "移动到" + "value": "Di chuyển đến" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Di chuyển đến" + "value": "移动到" } } } @@ -17553,16 +17659,16 @@ "value": "Sunucum" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "我的服务器" + "value": "Máy chủ của tôi" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Máy chủ của tôi" + "value": "我的服务器" } } } @@ -17597,16 +17703,16 @@ "value": "Ad:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "名称:" + "value": "Tên:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tên:" + "value": "名称:" } } } @@ -17664,16 +17770,16 @@ "value": "Başka bir sayfaya gitmek kaydedilmemiş değişiklikleri siler." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "导航到其他页面将丢弃所有未保存的更改。" + "value": "Chuyển sang trang khác sẽ huỷ tất cả thay đổi chưa lưu." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chuyển sang trang khác sẽ huỷ tất cả thay đổi chưa lưu." + "value": "导航到其他页面将丢弃所有未保存的更改。" } } } @@ -17731,16 +17837,16 @@ "value": "Ağ kullanılamıyor. Bağlantı sağlandığında değişiklikler eşitlenecek." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "网络不可用。恢复连接后将自动同步更改。" + "value": "Mạng không khả dụng. Các thay đổi sẽ được đồng bộ khi có kết nối trở lại." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mạng không khả dụng. Các thay đổi sẽ được đồng bộ khi có kết nối trở lại." + "value": "网络不可用。恢复连接后将自动同步更改。" } } } @@ -17885,16 +17991,16 @@ "value": "Yeni Sık Kullanılan" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "新建收藏" + "value": "Mục yêu thích mới" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mục yêu thích mới" + "value": "新建收藏" } } } @@ -17907,16 +18013,16 @@ "value": "Yeni Sık Kullanılan..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "新建收藏…" + "value": "Mục yêu thích mới..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mục yêu thích mới..." + "value": "新建收藏…" } } } @@ -17929,16 +18035,16 @@ "value": "Yeni Klasör" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "新建文件夹" + "value": "Thư mục mới" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thư mục mới" + "value": "新建文件夹" } } } @@ -18063,16 +18169,16 @@ "value": "Yeni Alt Klasör" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "新建子文件夹" + "value": "Thư mục con mới" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thư mục con mới" + "value": "新建子文件夹" } } } @@ -18107,16 +18213,16 @@ "value": "Yeni Tema" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "新建主题" + "value": "Chủ đề mới" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chủ đề mới" + "value": "新建主题" } } } @@ -18245,16 +18351,16 @@ "value": "Etkinleştirme bulunamadı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "未找到激活记录" + "value": "Không tìm thấy kích hoạt nào" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không tìm thấy kích hoạt nào" + "value": "未找到激活记录" } } } @@ -18555,16 +18661,16 @@ "value": "No Favorites" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "无收藏" + "value": "Không có mục yêu thích" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không có mục yêu thích" + "value": "无收藏" } } } @@ -18599,16 +18705,16 @@ "value": "No iCloud" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "无 iCloud" + "value": "Không có iCloud" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không có iCloud" + "value": "无 iCloud" } } } @@ -18754,16 +18860,16 @@ "value": "No Matching Favorites" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "无匹配的收藏" + "value": "Không có mục yêu thích phù hợp" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không có mục yêu thích phù hợp" + "value": "无匹配的收藏" } } } @@ -19463,16 +19569,16 @@ "value": "Not Available" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "不可用" + "value": "Không khả dụng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không khả dụng" + "value": "不可用" } } } @@ -19838,16 +19944,16 @@ "value": "NULL Değer" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "NULL 值" + "value": "Giá trị NULL" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giá trị NULL" + "value": "NULL 值" } } } @@ -19883,16 +19989,16 @@ "value": "Sayı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "数字" + "value": "Số" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Số" + "value": "数字" } } } @@ -20306,16 +20412,16 @@ "value": "Operator" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "运算符" + "value": "Toán tử" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Toán tử" + "value": "运算符" } } } @@ -20529,16 +20635,16 @@ "value": "Other Device" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "其他设备" + "value": "Thiết bị khác" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thiết bị khác" + "value": "其他设备" } } } @@ -20739,16 +20845,16 @@ "value": "Parolalar:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "密码:" + "value": "Mật khẩu:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mật khẩu:" + "value": "密码:" } } } @@ -21944,16 +22050,16 @@ "value": "Birincil Anahtar" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "主键" + "value": "Khoá chính" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Khoá chính" + "value": "主键" } } } @@ -21989,16 +22095,16 @@ "value": "Primary Text" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "主要文本" + "value": "Văn bản chính" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Văn bản chính" + "value": "主要文本" } } } @@ -22055,13 +22161,13 @@ "value": "Pro" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "Pro" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "Pro" @@ -22077,16 +22183,16 @@ "value": "Pro Lisans Gerekli" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "需要 Pro 许可证" + "value": "Yêu cầu giấy phép Pro" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Yêu cầu giấy phép Pro" + "value": "需要 Pro 许可证" } } } @@ -22099,16 +22205,16 @@ "value": "Pro license required for iCloud Sync" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "iCloud 同步需要 Pro 许可证" + "value": "Cần giấy phép Pro để sử dụng iCloud Sync" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cần giấy phép Pro để sử dụng iCloud Sync" + "value": "iCloud 同步需要 Pro 许可证" } } } @@ -22127,16 +22233,16 @@ "value": "Profil" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "配置" + "value": "Hồ sơ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hồ sơ" + "value": "配置" } } } @@ -22155,16 +22261,16 @@ "value": "Profil Adı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "配置名称" + "value": "Tên hồ sơ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tên hồ sơ" + "value": "配置名称" } } } @@ -22183,16 +22289,16 @@ "value": "Profil Ayarları" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "配置设置" + "value": "Cài đặt hồ sơ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cài đặt hồ sơ" + "value": "配置设置" } } } @@ -22278,16 +22384,16 @@ "value": "Lisans Satın Al" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "购买许可证" + "value": "Mua giấy phép" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mua giấy phép" + "value": "购买许可证" } } } @@ -22500,16 +22606,16 @@ "value": "Query History:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "查询历史:" + "value": "Lịch sử truy vấn:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lịch sử truy vấn:" + "value": "查询历史:" } } } @@ -22544,16 +22650,16 @@ "value": "Query:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "查询:" + "value": "Truy vấn:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Truy vấn:" + "value": "查询:" } } } @@ -23265,16 +23371,16 @@ "value": "Sunucudan lisans durumunu yenile" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "从服务器刷新许可证状态" + "value": "Làm mới trạng thái giấy phép từ máy chủ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Làm mới trạng thái giấy phép từ máy chủ" + "value": "从服务器刷新许可证状态" } } } @@ -23331,16 +23437,16 @@ "value": "Registry" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "插件仓库" + "value": "Kho plugin" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kho plugin" + "value": "插件仓库" } } } @@ -23469,16 +23575,16 @@ "value": "Yenile" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "续期" + "value": "Gia hạn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Gia hạn" + "value": "续期" } } } @@ -23497,16 +23603,16 @@ "value": "Lisansı Yenile" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "续期许可证" + "value": "Gia hạn giấy phép" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Gia hạn giấy phép" + "value": "续期许可证" } } } @@ -23519,16 +23625,16 @@ "value": "Renew License..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "续期许可证…" + "value": "Gia hạn giấy phép..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Gia hạn giấy phép..." + "value": "续期许可证…" } } } @@ -23585,16 +23691,16 @@ "value": "Sorguları çalıştırmadan önce onay veya Touch ID gerektirir." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "执行查询前需要确认或 Touch ID。" + "value": "Yêu cầu xác nhận hoặc Touch ID trước khi thực thi truy vấn." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Yêu cầu xác nhận hoặc Touch ID trước khi thực thi truy vấn." + "value": "执行查询前需要确认或 Touch ID。" } } } @@ -23629,16 +23735,16 @@ "value": "Zorunlu (doğrulamayı atla)" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "必需(跳过验证)" + "value": "Bắt buộc (bỏ qua xác minh)" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Bắt buộc (bỏ qua xác minh)" + "value": "必需(跳过验证)" } } } @@ -23811,16 +23917,16 @@ "value": "Doğrulamayı Tekrar Dene" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "重试验证" + "value": "Thử lại xác thực" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thử lại xác thực" + "value": "重试验证" } } } @@ -23923,16 +24029,16 @@ "value": "Root Level" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "根级别" + "value": "Cấp gốc" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cấp gốc" + "value": "根级别" } } } @@ -24040,16 +24146,16 @@ "value": "Row Heights" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "行高" + "value": "Chiều cao dòng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chiều cao dòng" + "value": "行高" } } } @@ -24084,16 +24190,16 @@ "value": "Row Number" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "行号" + "value": "Số thứ tự dòng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Số thứ tự dòng" + "value": "行号" } } } @@ -24262,16 +24368,16 @@ "value": "Güvenli Mod, Güvenli Mod (Tam) ve Salt Okunur için Pro lisans gereklidir." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "安全模式、安全模式(完整)和只读模式需要 Pro 许可证。" + "value": "Chế độ an toàn, Chế độ an toàn (toàn phần) và Chỉ đọc yêu cầu giấy phép Pro." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chế độ an toàn, Chế độ an toàn (toàn phần) và Chỉ đọc yêu cầu giấy phép Pro." + "value": "安全模式、安全模式(完整)和只读模式需要 Pro 许可证。" } } } @@ -24417,16 +24523,16 @@ "value": "Save as Favorite" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "保存为收藏" + "value": "Lưu vào yêu thích" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lưu vào yêu thích" + "value": "保存为收藏" } } } @@ -24439,16 +24545,16 @@ "value": "Save as Favorite..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "保存为收藏…" + "value": "Lưu vào yêu thích..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lưu vào yêu thích..." + "value": "保存为收藏…" } } } @@ -24556,16 +24662,16 @@ "value": "Geçerli Ayarları Profil Olarak Kaydet..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "将当前设置保存为配置…" + "value": "Lưu hiện tại thành hồ sơ..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lưu hiện tại thành hồ sơ..." + "value": "将当前设置保存为配置…" } } } @@ -24622,16 +24728,16 @@ "value": "Save frequently used queries\nfor quick access." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "保存常用查询\n以便快速访问。" + "value": "Lưu các truy vấn thường dùng\nđể truy cập nhanh." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lưu các truy vấn thường dùng\nđể truy cập nhanh." + "value": "保存常用查询\n以便快速访问。" } } } @@ -25156,16 +25262,16 @@ "value": "Secondary Text" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "次要文本" + "value": "Văn bản phụ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Văn bản phụ" + "value": "次要文本" } } } @@ -25184,13 +25290,13 @@ "value": "Secret Access Key" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "Secret Access Key" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "Secret Access Key" @@ -25206,16 +25312,16 @@ "value": "Section Header" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "分区标题" + "value": "Tiêu đề mục" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tiêu đề mục" + "value": "分区标题" } } } @@ -25251,13 +25357,13 @@ "value": "SELECT * FROM users WHERE id = 42;" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "SELECT * FROM users WHERE id = 42;" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "SELECT * FROM users WHERE id = 42;" @@ -25495,16 +25601,16 @@ "value": "Selected Item" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "选中项" + "value": "Mục đã chọn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mục đã chọn" + "value": "选中项" } } } @@ -25523,16 +25629,16 @@ "value": "Seçilen SSH profili artık mevcut değil." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "所选 SSH 配置已不存在。" + "value": "Hồ sơ SSH đã chọn không còn tồn tại." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hồ sơ SSH đã chọn không còn tồn tại." + "value": "所选 SSH 配置已不存在。" } } } @@ -25545,16 +25651,16 @@ "value": "Selection" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "选区" + "value": "Vùng chọn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Vùng chọn" + "value": "选区" } } } @@ -25668,13 +25774,13 @@ "value": "Session Token" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "Session Token" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "Session Token" @@ -25823,16 +25929,16 @@ "value": "Settings were changed" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "设置已更改" + "value": "Cài đặt đã bị thay đổi" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cài đặt đã bị thay đổi" + "value": "设置已更改" } } } @@ -25845,16 +25951,16 @@ "value": "Settings were changed on both this Mac and another device." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "设置在此 Mac 和另一台设备上均已更改。" + "value": "Cài đặt đã bị thay đổi trên cả máy Mac này và thiết bị khác." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cài đặt đã bị thay đổi trên cả máy Mac này và thiết bị khác." + "value": "设置在此 Mac 和另一台设备上均已更改。" } } } @@ -25867,16 +25973,16 @@ "value": "Settings:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "设置:" + "value": "Cài đặt:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cài đặt:" + "value": "设置:" } } } @@ -25889,16 +25995,16 @@ "value": "Shadows the SQL keyword '%@'" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "与 SQL 关键字 '%@' 冲突" + "value": "Trùng với từ khoá SQL '%@'" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Trùng với từ khoá SQL '%@'" + "value": "与 SQL 关键字 '%@' 冲突" } } } @@ -26266,16 +26372,16 @@ "value": "Sidebar" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "侧边栏" + "value": "Thanh bên" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thanh bên" + "value": "侧边栏" } } } @@ -26311,16 +26417,16 @@ "value": "Sign in to iCloud in System Settings to enable sync." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "请在系统设置中登录 iCloud 以启用同步。" + "value": "Đăng nhập iCloud trong Cài đặt hệ thống để bật đồng bộ." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đăng nhập iCloud trong Cài đặt hệ thống để bật đồng bộ." + "value": "请在系统设置中登录 iCloud 以启用同步。" } } } @@ -26333,16 +26439,16 @@ "value": "Sign in to iCloud to enable sync" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "登录 iCloud 以启用同步" + "value": "Đăng nhập iCloud để bật đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đăng nhập iCloud để bật đồng bộ" + "value": "登录 iCloud 以启用同步" } } } @@ -26488,16 +26594,16 @@ "value": "Slow" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "慢速" + "value": "Chậm" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chậm" + "value": "慢速" } } } @@ -26510,16 +26616,16 @@ "value": "Small" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "小" + "value": "Nhỏ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nhỏ" + "value": "小" } } } @@ -26532,16 +26638,16 @@ "value": "Smooth" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "平滑" + "value": "Mượt" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mượt" + "value": "平滑" } } } @@ -26576,16 +26682,16 @@ "value": "Bir sorun oluştu (hata %lld). Biraz sonra tekrar deneyin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "出现问题(错误 %lld)。请稍后重试。" + "value": "Đã xảy ra lỗi (mã %lld). Vui lòng thử lại sau." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã xảy ra lỗi (mã %lld). Vui lòng thử lại sau." + "value": "出现问题(错误 %lld)。请稍后重试。" } } } @@ -26643,16 +26749,16 @@ "value": "Spacing" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "间距" + "value": "Khoảng cách" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Khoảng cách" + "value": "间距" } } } @@ -26687,16 +26793,16 @@ "value": "Sponsor" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "赞助" + "value": "Tài trợ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tài trợ" + "value": "赞助" } } } @@ -26709,16 +26815,16 @@ "value": "Sponsor TablePro" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "赞助 TablePro" + "value": "Tài trợ TablePro" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tài trợ TablePro" + "value": "赞助 TablePro" } } } @@ -27094,16 +27200,16 @@ "value": "SSH Profili" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "SSH 配置" + "value": "Hồ sơ SSH" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hồ sơ SSH" + "value": "SSH 配置" } } } @@ -27122,16 +27228,16 @@ "value": "SSH Profilleri:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "SSH 配置:" + "value": "Hồ sơ SSH:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hồ sơ SSH:" + "value": "SSH 配置:" } } } @@ -27505,16 +27611,16 @@ "value": "Status Colors" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "状态颜色" + "value": "Màu trạng thái" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Màu trạng thái" + "value": "状态颜色" } } } @@ -27527,16 +27633,16 @@ "value": "Status Dot" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "状态指示点" + "value": "Chấm trạng thái" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chấm trạng thái" + "value": "状态指示点" } } } @@ -27555,16 +27661,16 @@ "value": "Durum:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "状态:" + "value": "Trạng thái:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Trạng thái:" + "value": "状态:" } } } @@ -27643,16 +27749,16 @@ "value": "String" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "字符串" + "value": "Chuỗi" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chuỗi" + "value": "字符串" } } } @@ -27894,16 +28000,16 @@ "value": "Satır İçi Yapılandırmaya Geç" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "切换为内联配置" + "value": "Chuyển sang cấu hình nội tuyến" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chuyển sang cấu hình nội tuyến" + "value": "切换为内联配置" } } } @@ -27916,16 +28022,16 @@ "value": "Sync" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "同步" + "value": "Đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ" + "value": "同步" } } } @@ -27938,16 +28044,16 @@ "value": "Sync Categories" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "同步分类" + "value": "Danh mục đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Danh mục đồng bộ" + "value": "同步分类" } } } @@ -27960,16 +28066,16 @@ "value": "Sync Conflict" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "同步冲突" + "value": "Xung đột đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xung đột đồng bộ" + "value": "同步冲突" } } } @@ -27982,16 +28088,16 @@ "value": "Sync connections, settings, and history across your Macs." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "在多台 Mac 之间同步连接、设置和历史记录。" + "value": "Đồng bộ kết nối, cài đặt và lịch sử giữa các máy Mac." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ kết nối, cài đặt và lịch sử giữa các máy Mac." + "value": "在多台 Mac 之间同步连接、设置和历史记录。" } } } @@ -28004,16 +28110,16 @@ "value": "Sync Error" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "同步错误" + "value": "Lỗi đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Lỗi đồng bộ" + "value": "同步错误" } } } @@ -28026,16 +28132,16 @@ "value": "Sync Now" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "立即同步" + "value": "Đồng bộ ngay" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ ngay" + "value": "立即同步" } } } @@ -28048,16 +28154,16 @@ "value": "Sync Off" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "同步已关闭" + "value": "Tắt đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tắt đồng bộ" + "value": "同步已关闭" } } } @@ -28070,16 +28176,16 @@ "value": "Sync paused — Pro license expired" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "同步已暂停 - Pro 许可证已过期" + "value": "Tạm dừng đồng bộ — giấy phép Pro đã hết hạn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tạm dừng đồng bộ — giấy phép Pro đã hết hạn" + "value": "同步已暂停 - Pro 许可证已过期" } } } @@ -28092,16 +28198,16 @@ "value": "Sync zone not found. A full sync will be performed." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "未找到同步区域。将执行完整同步。" + "value": "Không tìm thấy vùng đồng bộ. Sẽ thực hiện đồng bộ toàn bộ." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Không tìm thấy vùng đồng bộ. Sẽ thực hiện đồng bộ toàn bộ." + "value": "未找到同步区域。将执行完整同步。" } } } @@ -28114,16 +28220,16 @@ "value": "Synced" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "已同步" + "value": "Đã đồng bộ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đã đồng bộ" + "value": "已同步" } } } @@ -28136,16 +28242,16 @@ "value": "Syncing with iCloud..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "正在与 iCloud 同步…" + "value": "Đang đồng bộ với iCloud..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đang đồng bộ với iCloud..." + "value": "正在与 iCloud 同步…" } } } @@ -28158,16 +28264,16 @@ "value": "Syncing..." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "正在同步…" + "value": "Đang đồng bộ..." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đang đồng bộ..." + "value": "正在同步…" } } } @@ -28180,16 +28286,16 @@ "value": "Syncs connections, settings, and history across your Macs via iCloud." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "通过 iCloud 在多台 Mac 之间同步连接、设置和历史记录。" + "value": "Đồng bộ kết nối, cài đặt và lịch sử giữa các máy Mac qua iCloud." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ kết nối, cài đặt và lịch sử giữa các máy Mac qua iCloud." + "value": "通过 iCloud 在多台 Mac 之间同步连接、设置和历史记录。" } } } @@ -28208,16 +28314,16 @@ "value": "Parolaları iCloud Keychain ile senkronize eder (uçtan uca şifreli)." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "通过 iCloud 钥匙串同步密码(端到端加密)。" + "value": "Đồng bộ mật khẩu qua iCloud Keychain (mã hoá đầu cuối)." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đồng bộ mật khẩu qua iCloud Keychain (mã hoá đầu cuối)." + "value": "通过 iCloud 钥匙串同步密码(端到端加密)。" } } } @@ -28230,16 +28336,16 @@ "value": "Syntax Colors" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "语法颜色" + "value": "Màu cú pháp" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Màu cú pháp" + "value": "语法颜色" } } } @@ -28521,16 +28627,16 @@ "value": "TablePro Website" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "TablePro 网站" + "value": "Trang web TablePro" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Trang web TablePro" + "value": "TablePro 网站" } } } @@ -28745,16 +28851,16 @@ "value": "Tertiary Text" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "辅助文本" + "value": "Văn bản thứ ba" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Văn bản thứ ba" + "value": "辅助文本" } } } @@ -28811,16 +28917,16 @@ "value": "Metin" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "文本" + "value": "Văn bản" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Văn bản" + "value": "文本" } } } @@ -28833,16 +28939,16 @@ "value": "Bu geçerli bir lisans anahtarı gibi görünmüyor. Yazım hatalarını kontrol edip tekrar deneyin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "这看起来不是有效的许可证密钥。请检查是否有拼写错误后重试。" + "value": "Mã giấy phép không hợp lệ. Kiểm tra lỗi chính tả và thử lại." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mã giấy phép không hợp lệ. Kiểm tra lỗi chính tả và thử lại." + "value": "这看起来不是有效的许可证密钥。请检查是否有拼写错误后重试。" } } } @@ -28927,16 +29033,16 @@ "value": "The folder \"%@\" will be deleted. Items inside will be moved to the parent level." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "文件夹 \"%@\" 将被删除。其中的项目将移至上一级。" + "value": "Thư mục \"%@\" sẽ bị xoá. Các mục bên trong sẽ được chuyển lên cấp cha." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thư mục \"%@\" sẽ bị xoá. Các mục bên trong sẽ được chuyển lên cấp cha." + "value": "文件夹 \"%@\" 将被删除。其中的项目将移至上一级。" } } } @@ -28977,16 +29083,16 @@ "value": "Lisans askıya alındı." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证已被暂停。" + "value": "Giấy phép đã bị đình chỉ." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép đã bị đình chỉ." + "value": "许可证已被暂停。" } } } @@ -28999,16 +29105,16 @@ "value": "Lisansın süresi doldu." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证已过期。" + "value": "Giấy phép đã hết hạn." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép đã hết hạn." + "value": "许可证已过期。" } } } @@ -29021,16 +29127,16 @@ "value": "Lisans anahtarı geçersiz." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "许可证密钥无效。" + "value": "Mã giấy phép không hợp lệ." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Mã giấy phép không hợp lệ." + "value": "许可证密钥无效。" } } } @@ -29176,16 +29282,16 @@ "value": "This is a built-in theme." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "这是内置主题。" + "value": "Đây là chủ đề mặc định." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đây là chủ đề mặc định." + "value": "这是内置主题。" } } } @@ -29198,16 +29304,16 @@ "value": "This is a registry theme." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "这是插件仓库主题。" + "value": "Đây là chủ đề từ kho plugin." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Đây là chủ đề từ kho plugin." + "value": "这是插件仓库主题。" } } } @@ -29220,16 +29326,16 @@ "value": "This keyword is already in use" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此关键字已被使用" + "value": "Từ khoá này đã được sử dụng" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Từ khoá này đã được sử dụng" + "value": "此关键字已被使用" } } } @@ -29242,16 +29348,16 @@ "value": "Bu lisans askıya alındı. Yardım için destek ile iletişime geçin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此许可证已被暂停。请联系支持团队获取帮助。" + "value": "Giấy phép này đã bị đình chỉ. Liên hệ hỗ trợ để được giúp đỡ." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép này đã bị đình chỉ. Liên hệ hỗ trợ để được giúp đỡ." + "value": "此许可证已被暂停。请联系支持团队获取帮助。" } } } @@ -29264,16 +29370,16 @@ "value": "Bu lisansın süresi doldu. Pro özellikleri kullanmaya devam etmek için yenileyin." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此许可证已过期。请续期以继续使用 Pro 功能。" + "value": "Giấy phép này đã hết hạn. Gia hạn để tiếp tục sử dụng các tính năng Pro." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép này đã hết hạn. Gia hạn để tiếp tục sử dụng các tính năng Pro." + "value": "此许可证已过期。请续期以继续使用 Pro 功能。" } } } @@ -29286,16 +29392,16 @@ "value": "Bu lisans etkinleştirme sınırına ulaştı. Önce başka bir Mac'i devre dışı bırakın." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此许可证已达到激活上限。请先停用另一台 Mac。" + "value": "Giấy phép này đã đạt giới hạn kích hoạt. Hãy huỷ kích hoạt một máy Mac khác trước." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép này đã đạt giới hạn kích hoạt. Hãy huỷ kích hoạt một máy Mac khác trước." + "value": "此许可证已达到激活上限。请先停用另一台 Mac。" } } } @@ -29308,16 +29414,16 @@ "value": "This Mac" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此 Mac" + "value": "Máy Mac này" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Máy Mac này" + "value": "此 Mac" } } } @@ -29330,16 +29436,16 @@ "value": "Bu makine bu lisans için etkinleştirilmemiş." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此设备未激活该许可证。" + "value": "Máy này chưa được kích hoạt cho giấy phép này." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Máy này chưa được kích hoạt cho giấy phép này." + "value": "此设备未激活该许可证。" } } } @@ -29352,16 +29458,16 @@ "value": "Bu makine etkinleştirilmemiş." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此设备未激活。" + "value": "Máy này chưa được kích hoạt." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Máy này chưa được kích hoạt." + "value": "此设备未激活。" } } } @@ -29446,16 +29552,16 @@ "value": "Bu profil kalıcı olarak silinecek." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "此配置将被永久删除。" + "value": "Hồ sơ này sẽ bị xoá vĩnh viễn." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hồ sơ này sẽ bị xoá vĩnh viễn." + "value": "此配置将被永久删除。" } } } @@ -29663,16 +29769,16 @@ "value": "Katman:" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "等级:" + "value": "Hạng:" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Hạng:" + "value": "等级:" } } } @@ -29707,16 +29813,16 @@ "value": "Tiny" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "极小" + "value": "Rất nhỏ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Rất nhỏ" + "value": "极小" } } } @@ -29729,16 +29835,16 @@ "value": "Tiny Dot" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "极小圆点" + "value": "Chấm nhỏ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chấm nhỏ" + "value": "极小圆点" } } } @@ -29751,16 +29857,16 @@ "value": "Title 2" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "标题 2" + "value": "Tiêu đề 2" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tiêu đề 2" + "value": "标题 2" } } } @@ -29773,16 +29879,16 @@ "value": "Title 3" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "标题 3" + "value": "Tiêu đề 3" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Tiêu đề 3" + "value": "标题 3" } } } @@ -29795,16 +29901,16 @@ "value": "TLS Mode" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "TLS 模式" + "value": "Chế độ TLS" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Chế độ TLS" + "value": "TLS 模式" } } } @@ -30176,16 +30282,16 @@ "value": "Toolbar" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "工具栏" + "value": "Thanh công cụ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Thanh công cụ" + "value": "工具栏" } } } @@ -30573,16 +30679,16 @@ "value": "Typography" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "排版" + "value": "Kiểu chữ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Kiểu chữ" + "value": "排版" } } } @@ -31485,16 +31591,16 @@ "value": "CA Doğrula" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "验证 CA" + "value": "Xác minh CA" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xác minh CA" + "value": "验证 CA" } } } @@ -31529,16 +31635,16 @@ "value": "Kimliği Doğrula" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "验证身份" + "value": "Xác minh danh tính" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xác minh danh tính" + "value": "验证身份" } } } @@ -31756,16 +31862,16 @@ "value": "Uyarı" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "警告" + "value": "Cảnh báo" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Cảnh báo" + "value": "警告" } } } @@ -31941,16 +32047,16 @@ "value": "When enabled, this favorite is visible in all connections" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "启用后,此收藏在所有连接中可见" + "value": "Khi bật, mục yêu thích này hiển thị trong tất cả kết nối" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Khi bật, mục yêu thích này hiển thị trong tất cả kết nối" + "value": "启用后,此收藏在所有连接中可见" } } } @@ -32007,16 +32113,16 @@ "value": "Window Background" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "窗口背景" + "value": "Nền cửa sổ" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Nền cửa sổ" + "value": "窗口背景" } } } @@ -32119,16 +32225,16 @@ "value": "XLSX Dışa Aktarma" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "XLSX 导出" + "value": "Xuất XLSX" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xuất XLSX" + "value": "XLSX 导出" } } } @@ -32141,16 +32247,16 @@ "value": "XLSX dışa aktarma için Pro lisans gereklidir." } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "XLSX 导出需要 Pro 许可证。" + "value": "Xuất XLSX yêu cầu giấy phép Pro." } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Xuất XLSX yêu cầu giấy phép Pro." + "value": "XLSX 导出需要 Pro 许可证。" } } } @@ -32163,13 +32269,13 @@ "value": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", "value": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", "value": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" @@ -32383,16 +32489,16 @@ "value": "Lisansınızın süresi doldu" } }, - "zh-Hans": { + "vi": { "stringUnit": { "state": "translated", - "value": "您的许可证已过期" + "value": "Giấy phép của bạn đã hết hạn" } }, - "vi": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Giấy phép của bạn đã hết hạn" + "value": "您的许可证已过期" } } } diff --git a/TablePro/TableProApp.swift b/TablePro/TableProApp.swift index b1cef3e5d..2d5c47882 100644 --- a/TablePro/TableProApp.swift +++ b/TablePro/TableProApp.swift @@ -245,6 +245,11 @@ struct AppMenuCommands: Commands { .optionalKeyboardShortcut(shortcut(for: .export)) .disabled(!appState.isConnected) + Button("Export Results...") { + actions?.exportQueryResults() + } + .disabled(!appState.isConnected) + if appState.currentDatabaseType.map({ PluginManager.shared.supportsImport(for: $0) }) ?? true { Button("Import...") { actions?.importTables() diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift index 9125ce930..806b1b9f8 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -15,8 +15,7 @@ import UniformTypeIdentifiers /// Main export dialog view struct ExportDialog: View { @Binding var isPresented: Bool - let connection: DatabaseConnection - let preselectedTables: Set + let mode: ExportMode // MARK: - State @@ -38,17 +37,47 @@ struct ExportDialog: View { @State private var exportServiceState = ExportServiceState() + // MARK: - Mode Helpers + + private var connection: DatabaseConnection { + switch mode { + case .tables(let conn, _): return conn + case .queryResults(let conn, _, _): return conn + } + } + + private var isQueryResultsMode: Bool { + if case .queryResults = mode { return true } + return false + } + + private var queryResultsRowCount: Int { + if case .queryResults(_, let rowBuffer, _) = mode { + return rowBuffer.rows.count + } + return 0 + } + + private var preselectedTables: Set { + if case .tables(_, let tables) = mode { + return tables + } + return [] + } + // MARK: - Body var body: some View { VStack(spacing: 0) { // Content HStack(spacing: 0) { - // Left: Table tree view - tableSelectionView - .frame(width: leftPanelWidth) + if !isQueryResultsMode { + // Left: Table tree view + tableSelectionView + .frame(width: leftPanelWidth) - Divider() + Divider() + } // Right: Export options exportOptionsView @@ -83,7 +112,14 @@ struct ExportDialog: View { } } .task { - await loadDatabaseItems() + if isQueryResultsMode { + if case .queryResults(_, _, let suggestedFileName) = mode { + config.fileName = suggestedFileName + } + isLoading = false + } else { + await loadDatabaseItems() + } } .sheet(isPresented: $showProgressDialog) { ExportProgressView( @@ -151,7 +187,7 @@ struct ExportDialog: View { } private var dialogWidth: CGFloat { - leftPanelWidth + 280 + isQueryResultsMode ? 280 : leftPanelWidth + 280 } // MARK: - Table Selection View @@ -251,6 +287,10 @@ struct ExportDialog: View { } .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) .buttonStyle(.link) + } else if isQueryResultsMode { + Text("\(queryResultsRowCount) row\(queryResultsRowCount == 1 ? "" : "s") to export") + .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) + .foregroundStyle(.secondary) } else { Text("\(exportableCount) table\(exportableCount == 1 ? "" : "s") to export") .font(.system(size: ThemeEngine.shared.activeTheme.typography.small)) @@ -346,7 +386,7 @@ struct ExportDialog: View { } .buttonStyle(.borderedProminent) .keyboardShortcut(.return, modifiers: []) - .disabled(exportableCount == 0 || isExporting || !isFileNameValid || availableFormats.isEmpty || isProGatedFormat(config.formatId)) + .disabled(isExportDisabled) } .padding(.horizontal, 16) .padding(.vertical, 12) @@ -377,6 +417,16 @@ struct ExportDialog: View { currentPlugin?.currentFileExtension ?? config.formatId } + private var isExportDisabled: Bool { + if isExporting || !isFileNameValid || availableFormats.isEmpty || isProGatedFormat(config.formatId) { + return true + } + if isQueryResultsMode { + return queryResultsRowCount == 0 + } + return exportableCount == 0 + } + private static let formatDisplayOrder = ["csv", "json", "sql", "xlsx", "mql"] private static let proFormatIds: Set = ["xlsx"] @@ -640,13 +690,21 @@ struct ExportDialog: View { } let formatName = currentPlugin.map { type(of: $0).formatDisplayName } ?? config.formatId.uppercased() - savePanel.message = "Export \(exportableCount) table(s) to \(formatName)" + if isQueryResultsMode { + savePanel.message = String(localized: "Export \(queryResultsRowCount) row(s) to \(formatName)") + } else { + savePanel.message = String(localized: "Export \(exportableCount) table(s) to \(formatName)") + } savePanel.begin { response in guard response == .OK, let url = savePanel.url else { return } Task { - await startExport(to: url) + if self.isQueryResultsMode { + await self.startQueryResultsExport(to: url) + } else { + await self.startExport(to: url) + } } } } @@ -702,6 +760,43 @@ struct ExportDialog: View { } } + @MainActor + private func startQueryResultsExport(to url: URL) async { + guard case .queryResults(_, let rowBuffer, _) = mode else { return } + + isExporting = true + exportedFileURL = url + + let service = ExportService(databaseType: connection.type) + exportServiceState.setService(service) + showProgressDialog = true + + do { + try await service.exportQueryResults( + rowBuffer: rowBuffer, + config: config, + to: url + ) + + showProgressDialog = false + isExporting = false + + if hideSuccessDialog { + isPresented = false + } else { + showSuccessDialog = true + } + } catch { + showProgressDialog = false + isExporting = false + AlertHelper.showErrorSheet( + title: String(localized: "Export Error"), + message: error.localizedDescription, + window: nil + ) + } + } + private func openContainingFolder() { guard let url = exportedFileURL else { return } NSWorkspace.shared.activateFileViewerSelecting([url]) @@ -741,7 +836,6 @@ final class ExportServiceState { return ExportDialog( isPresented: .constant(true), - connection: connection, - preselectedTables: ["users"] + mode: .tables(connection: connection, preselectedTables: ["users"]) ) } diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift index 111bb21a6..7c5865380 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift @@ -62,6 +62,11 @@ extension MainContentCoordinator { activeSheet = .exportDialog } + func openExportQueryResultsDialog() { + guard let tab = tabManager.selectedTab, !tab.rowBuffer.rows.isEmpty else { return } + activeSheet = .exportQueryResults + } + func openImportDialog() { guard !safeModeLevel.blocksAllWrites else { return } guard PluginManager.shared.supportsImport(for: connection.type) else { diff --git a/TablePro/Views/Main/MainContentCommandActions.swift b/TablePro/Views/Main/MainContentCommandActions.swift index e37fa14ef..b0e2f183d 100644 --- a/TablePro/Views/Main/MainContentCommandActions.swift +++ b/TablePro/Views/Main/MainContentCommandActions.swift @@ -158,6 +158,8 @@ final class MainContentCommandActions { observeKeyWindowOnly(.duplicateRow) { [weak self] _ in self?.duplicateRow() } + observeKeyWindowOnly(.exportQueryResults) { [weak self] _ in self?.exportQueryResults() } + // Note: .copySelectedRows and .pasteRows observers call the data-grid // path directly (not the public methods) to avoid an infinite loop — // the public methods re-post these notifications for structure view. @@ -448,6 +450,10 @@ final class MainContentCommandActions { coordinator?.openExportDialog() } + func exportQueryResults() { + coordinator?.openExportQueryResultsDialog() + } + func importTables() { coordinator?.openImportDialog() } diff --git a/TablePro/Views/Main/MainContentCoordinator.swift b/TablePro/Views/Main/MainContentCoordinator.swift index 5f202f63b..9ad9060ec 100644 --- a/TablePro/Views/Main/MainContentCoordinator.swift +++ b/TablePro/Views/Main/MainContentCoordinator.swift @@ -36,6 +36,7 @@ enum ActiveSheet: Identifiable { case exportDialog case importDialog case quickSwitcher + case exportQueryResults var id: Self { self } } diff --git a/TablePro/Views/Main/MainContentView.swift b/TablePro/Views/Main/MainContentView.swift index cc0cdbe10..48df09dc3 100644 --- a/TablePro/Views/Main/MainContentView.swift +++ b/TablePro/Views/Main/MainContentView.swift @@ -142,9 +142,22 @@ struct MainContentView: View { case .exportDialog: ExportDialog( isPresented: dismissBinding, - connection: connection, - preselectedTables: Set(sidebarState.selectedTables.map(\.name)) + mode: .tables( + connection: connection, + preselectedTables: Set(sidebarState.selectedTables.map(\.name)) + ) ) + case .exportQueryResults: + if let tab = coordinator.tabManager.selectedTab { + ExportDialog( + isPresented: dismissBinding, + mode: .queryResults( + connection: connection, + rowBuffer: tab.rowBuffer, + suggestedFileName: tab.tableName ?? "query_results" + ) + ) + } case .importDialog: ImportDialog( isPresented: dismissBinding, diff --git a/TablePro/Views/Results/TableRowViewWithMenu.swift b/TablePro/Views/Results/TableRowViewWithMenu.swift index f5a7f2517..5d17e1c71 100644 --- a/TablePro/Views/Results/TableRowViewWithMenu.swift +++ b/TablePro/Views/Results/TableRowViewWithMenu.swift @@ -132,6 +132,17 @@ final class TableRowViewWithMenu: NSTableRowView { menu.addItem(setValueItem) } + // Export Results + menu.addItem(NSMenuItem.separator()) + + let exportItem = NSMenuItem( + title: String(localized: "Export Results..."), + action: #selector(exportResults), + keyEquivalent: "" + ) + exportItem.target = self + menu.addItem(exportItem) + // Duplicate & Delete if coordinator.isEditable { let duplicateItem = NSMenuItem( @@ -248,6 +259,10 @@ final class TableRowViewWithMenu: NSTableRowView { coordinator.copyRowsAsUpdate(at: indices) } + @objc private func exportResults() { + NotificationCenter.default.post(name: .exportQueryResults, object: nil) + } + @objc private func copyAsJson() { guard let coordinator else { return } let indices: Set = !coordinator.selectedRowIndices.isEmpty diff --git a/docs/features/import-export.mdx b/docs/features/import-export.mdx index 9b41ae09e..c62876be9 100644 --- a/docs/features/import-export.mdx +++ b/docs/features/import-export.mdx @@ -255,11 +255,15 @@ Export data from query results or tables: ### Exporting Query Results -To export the results of a specific query: +To export the results of a specific query to a file: -1. Write and execute your query -2. Click **Export** while results are displayed -3. Only the displayed results are exported +1. Write and execute your query in the SQL editor +2. Right-click on the results grid and select **Export Results...**, or go to **File** > **Export Results...** +3. Choose your export format (CSV, JSON, SQL, XLSX, or MQL) +4. Configure format-specific options and filename +5. Click **Export** + +The export dialog shows the row count and uses a simplified layout without the table selection tree, since only the in-memory query results are exported. For large exports, use pagination or LIMIT in your query to control the output size. diff --git a/docs/vi/features/import-export.mdx b/docs/vi/features/import-export.mdx index 22770e3ec..045e479ac 100644 --- a/docs/vi/features/import-export.mdx +++ b/docs/vi/features/import-export.mdx @@ -251,11 +251,15 @@ Export dữ liệu từ kết quả query hoặc bảng: ### Export Kết Quả Query -Để export kết quả query cụ thể: +Để export kết quả query ra file: -1. Viết và chạy query -2. Click **Export** khi kết quả hiển thị -3. Chỉ kết quả hiện tại được export +1. Viết và chạy query trong trình soạn thảo SQL +2. Chuột phải vào bảng kết quả và chọn **Export Results...**, hoặc vào **File** > **Export Results...** +3. Chọn định dạng export (CSV, JSON, SQL, XLSX hoặc MQL) +4. Cấu hình tùy chọn và tên file +5. Click **Export** + +Hộp thoại export hiển thị số hàng và dùng giao diện đơn giản không có tree chọn bảng, vì chỉ export kết quả query trong bộ nhớ. Với export lớn, dùng phân trang hoặc LIMIT trong query để kiểm soát kích thước output. diff --git a/docs/zh/features/import-export.mdx b/docs/zh/features/import-export.mdx index cdb80df65..5f0dd78ac 100644 --- a/docs/zh/features/import-export.mdx +++ b/docs/zh/features/import-export.mdx @@ -251,11 +251,15 @@ description: 导出为 CSV、JSON、SQL、MQL 或 XLSX 格式,导入 SQL 文 ### 导出查询结果 -要导出特定查询的结果: +要将查询结果导出为文件: -1. 编写并执行查询 -2. 在结果显示时点击 **Export** -3. 仅导出显示的结果 +1. 在 SQL 编辑器中编写并执行查询 +2. 右键点击结果网格并选择 **Export Results...**,或进入 **File** > **Export Results...** +3. 选择导出格式(CSV、JSON、SQL、XLSX 或 MQL) +4. 配置格式选项和文件名 +5. 点击 **Export** + +导出对话框显示行数,并使用简化的布局(不含表选择树),因为仅导出内存中的查询结果。 对于大批量导出,使用分页或在查询中添加 LIMIT 来控制输出大小。