From 451a7fe6b2f575a3f18e1307a9b54a4aa5cb23f1 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 6 Nov 2026 22:37:29 +0700 Subject: [PATCH 1/5] feat(platforms): Linux carries its channels, distributions, packages and release before it ships --- resources/data/platforms.json | 226 +++++++++++++++++++++-- resources/js/lib/data/platforms.ts | 109 +++++++---- tests/Feature/Data/PlatformsDataTest.php | 39 +++- 3 files changed, 330 insertions(+), 44 deletions(-) diff --git a/resources/data/platforms.json b/resources/data/platforms.json index 1c614fe2..ed9c4145 100644 --- a/resources/data/platforms.json +++ b/resources/data/platforms.json @@ -4,15 +4,22 @@ { "id": "mac", "status": "released", - "deviceNames": ["Mac"], + "deviceNames": [ + "Mac" + ], "page": null, "requirements": { - "systems": ["macOS"], + "systems": [ + "macOS" + ], "minVersion": "13.0", "displayVersion": "13", "releaseName": "Ventura" }, - "architectures": ["arm64", "x86_64"], + "architectures": [ + "arm64", + "x86_64" + ], "universalBinary": true, "assetTemplate": "TablePro-{version}.dmg", "destinations": [ @@ -36,15 +43,29 @@ "publishedAt": "2026-10-19" }, "floorVersion": "0.82.0", - "appLanguages": ["en", "vi", "zh-Hans", "zh-Hant", "ko", "tr", "fr"] + "appLanguages": [ + "en", + "vi", + "zh-Hans", + "zh-Hant", + "ko", + "tr", + "fr" + ] }, { "id": "ios", "status": "released", - "deviceNames": ["iPhone", "iPad"], + "deviceNames": [ + "iPhone", + "iPad" + ], "page": "/ios", "requirements": { - "systems": ["iOS", "iPadOS"], + "systems": [ + "iOS", + "iPadOS" + ], "minVersion": "18.0", "displayVersion": "18", "releaseName": null @@ -61,12 +82,193 @@ "build": "22", "publishedAt": "2026-09-22" }, - "iosEngines": ["mysql", "mariadb", "tidb", "oceanbase", "postgresql", "sqlite", "duckdb", "redis", "sqlserver", "oracle"], - "storefrontExclusions": ["at", "be", "bg", "cy", "cz", "de", "dk", "ee", "es", "fi", "fr", "gr", "hr", "hu", "ie", "it", "li", "lt", "lu", "lv", "mt", "nl", "pl", "pt", "ro", "se", "si", "sk"], - "price": { "amount": 0, "inAppPurchases": false }, - "appLanguages": ["en", "vi", "ko", "zh-Hans", "zh-Hant"] + "iosEngines": [ + "mysql", + "mariadb", + "tidb", + "oceanbase", + "postgresql", + "sqlite", + "duckdb", + "redis", + "sqlserver", + "oracle" + ], + "storefrontExclusions": [ + "at", + "be", + "bg", + "cy", + "cz", + "de", + "dk", + "ee", + "es", + "fi", + "fr", + "gr", + "hr", + "hu", + "ie", + "it", + "li", + "lt", + "lu", + "lv", + "mt", + "nl", + "pl", + "pt", + "ro", + "se", + "si", + "sk" + ], + "price": { + "amount": 0, + "inAppPurchases": false + }, + "appLanguages": [ + "en", + "vi", + "ko", + "zh-Hans", + "zh-Hant" + ] }, - { "id": "linux", "status": "prototype" }, - { "id": "windows", "status": "none" } + { + "id": "linux", + "status": "prototype", + "channel": "beta", + "deviceNames": [ + "Linux" + ], + "page": null, + "distributions": [ + { + "id": "ubuntu", + "name": "Ubuntu", + "versions": [ + "26.04", + "26.10" + ], + "via": "ppa" + }, + { + "id": "fedora", + "name": "Fedora", + "versions": [ + "44", + "45" + ], + "via": "copr" + }, + { + "id": "other", + "name": null, + "versions": [], + "via": "flatpak" + } + ], + "desktop": { + "name": "GNOME", + "version": "50" + }, + "architectures": [ + "x86_64", + "arm64" + ], + "destinations": [ + { + "kind": "ppa", + "channel": "beta", + "repository": "ppa:tablepro/tablepro-beta", + "commands": [ + "sudo add-apt-repository ppa:tablepro/tablepro-beta", + "sudo apt install tablepro" + ], + "update": "sudo apt upgrade" + }, + { + "kind": "copr", + "channel": "beta", + "repository": "@tablepro/tablepro-beta", + "commands": [ + "sudo dnf copr enable @tablepro/tablepro-beta", + "sudo dnf install tablepro" + ], + "update": "sudo dnf upgrade" + }, + { + "kind": "flatpak", + "channel": "beta", + "appId": "app.tablepro.TablePro", + "branch": "beta", + "url": "https://flatpak.tablepro.app/beta-repo/app.tablepro.TablePro.flatpakref", + "commands": [ + "flatpak install --from https://flatpak.tablepro.app/beta-repo/app.tablepro.TablePro.flatpakref", + "flatpak run --branch=beta app.tablepro.TablePro" + ], + "update": "flatpak update" + }, + { + "kind": "packages", + "url": "https://github.com/TableProApp/TablePro/releases", + "tagPrefix": "linux-v", + "tagTemplate": "https://github.com/TableProApp/TablePro/releases/tag/linux-v{version}", + "assets": [ + { + "format": "deb", + "template": "tablepro-{version}-ubuntu-{series}-{arch}.deb", + "series": [ + "26.04", + "26.10" + ], + "arch": { + "x86_64": "amd64", + "arm64": "arm64" + } + }, + { + "format": "rpm", + "template": "tablepro-{version}-fedora-{series}-{arch}.rpm", + "series": [ + "44", + "45" + ], + "arch": { + "x86_64": "x86_64", + "arm64": "aarch64" + } + }, + { + "format": "flatpak", + "template": "tablepro-{version}-{arch}.flatpak", + "series": [], + "arch": { + "x86_64": "x86_64", + "arm64": "aarch64" + } + } + ], + "checksums": "SHA256SUMS", + "verify": "gh attestation verify {file} --repo TableProApp/TablePro" + } + ], + "release": { + "version": "0.1.0-beta.1", + "build": null, + "publishedAt": "2026-11-06" + }, + "appLanguages": [ + "en", + "vi" + ], + "evidence": "TableProApp/TablePro:linux/README.md:39-116, linux/build-aux/packaging/release-matrix.json, linux/docs/decisions/0041-beta-repositories-and-release-packages.md:55-72, PR #4075" + }, + { + "id": "windows", + "status": "none" + } ] } diff --git a/resources/js/lib/data/platforms.ts b/resources/js/lib/data/platforms.ts index 91626bfc..d5e5f2d2 100644 --- a/resources/js/lib/data/platforms.ts +++ b/resources/js/lib/data/platforms.ts @@ -22,89 +22,131 @@ export type PlatformStatus = 'released' | 'prototype' | 'none'; export type MacArchitecture = 'arm64' | 'x86_64'; +export type LinuxArchitecture = 'x86_64' | 'arm64'; + +export type LinuxChannel = 'beta' | 'stable'; + export interface Requirements { - /** Apple's system names, never translated: `["iOS", "iPadOS"]`. */ + // Apple's system names, never translated: `["iOS", "iPadOS"]`. systems: string[]; - /** The exact floor, `13.0`. */ minVersion: string; - /** How copy writes it, `13`. */ + // How copy writes the floor: `13` for `13.0`. displayVersion: string; - /** `Ventura`; null where Apple gives the release no name. */ + // `Ventura`; null where Apple gives the release no name. releaseName: string | null; } +export interface LinuxPackageAsset { + format: 'deb' | 'rpm' | 'flatpak'; + // `{version}`, `{series}` and `{arch}` fill in the GitHub release file name. + template: string; + series: string[]; + // The architecture as each format spells it: amd64 for a .deb, x86_64 for an .rpm. + arch: Record; +} + export type Destination = | { kind: 'dmg'; urlTemplate: string } | { kind: 'homebrew'; command: string; url: string } | { kind: 'releases'; url: string } - | { kind: 'app-store'; url: string; appId: string }; + | { kind: 'app-store'; url: string; appId: string } + | { kind: 'ppa'; channel: LinuxChannel; repository: string; commands: string[]; update: string } + | { kind: 'copr'; channel: LinuxChannel; repository: string; commands: string[]; update: string } + | { kind: 'flatpak'; channel: LinuxChannel; appId: string; branch: string; url: string; commands: string[]; update: string } + | { + kind: 'packages'; + url: string; + tagPrefix: string; + tagTemplate: string; + assets: LinuxPackageAsset[]; + checksums: string; + verify: string; + }; export type DestinationKind = Destination['kind']; export interface Release { version: string; - build: string; - /** `YYYY-MM-DD`. Format it in PHP for anything rendered on the server. */ + build: string | null; + // `YYYY-MM-DD`. Format it in PHP for anything rendered on the server. publishedAt: string; } interface ReleasedPlatformBase { status: 'released'; - /** Apple product names, never translated: `["iPhone", "iPad"]`. */ + // Product names, never translated: `["iPhone", "iPad"]`, `["Linux"]`. deviceNames: string[]; - /** The platform's own page (`/ios`), or null when `/download` is its page. */ + // The platform's own page (`/ios`), or null when `/download` is its page. page: string | null; - requirements: Requirements; destinations: Destination[]; - /** The last verified release. The download page prefers the live one. */ + // The last verified release. The download page prefers the live one. release: Release; - /** BCP 47 tags of the app's own UI languages, in the app's order. */ + // BCP 47 tags of the app's own UI languages, in the app's order. appLanguages: string[]; } -export interface MacPlatform extends ReleasedPlatformBase { +interface ApplePlatformBase extends ReleasedPlatformBase { + requirements: Requirements; +} + +export interface MacPlatform extends ApplePlatformBase { id: 'mac'; - /** The chips the app runs on natively, in display order. */ architectures: MacArchitecture[]; - /** One DMG runs on every architecture. */ universalBinary: boolean; - /** The DMG file name, with `{version}` for the release version. */ + // The DMG file name, with `{version}` for the release version. assetTemplate: string; - /** The oldest version any channel still serves. */ + // The oldest version any channel still serves. floorVersion: string; } -export interface IosPlatform extends ReleasedPlatformBase { +export interface IosPlatform extends ApplePlatformBase { id: 'ios'; - /** Engine ids offered in the iOS connection picker, in its order. Name them; never count them. */ + // Engine ids offered in the iOS connection picker, in its order. Name them; never count them. iosEngines: string[]; - /** Storefront codes the listing is missing from. Read by `release:check` only; never rendered. */ + // Read by `release:check` only; never rendered. storefrontExclusions: string[]; price: { amount: number; inAppPurchases: boolean }; } +// A distribution with native packages, or `via: 'flatpak'` with no name for every other one. +export interface LinuxDistribution { + id: string; + name: string | null; + versions: string[]; + via: 'ppa' | 'copr' | 'flatpak'; +} + +// Linux carries its data before it ships, so pages can be built against it; `status` alone decides whether it shows. +export interface LinuxPlatform extends Omit { + id: 'linux'; + status: 'released' | 'prototype'; + channel: LinuxChannel; + distributions: LinuxDistribution[]; + desktop: { name: string; version: string }; + architectures: LinuxArchitecture[]; +} + export interface UnreleasedPlatform { - id: 'linux' | 'windows'; + id: 'windows'; status: 'prototype' | 'none'; } -export type ReleasedPlatform = MacPlatform | IosPlatform; +export type ApplePlatform = MacPlatform | IosPlatform; + +export type ReleasedLinuxPlatform = LinuxPlatform & { status: 'released' }; + +export type ReleasedPlatform = ApplePlatform | ReleasedLinuxPlatform; -export type Platform = ReleasedPlatform | UnreleasedPlatform; +export type Platform = ApplePlatform | LinuxPlatform | UnreleasedPlatform; export interface PlatformsData { verifiedAt: string; platforms: Platform[]; } -/** - * The single cast from the JSON import, whose string unions TypeScript widens - * to `string`. `tests/Feature/Data/PlatformsDataTest.php` validates the file - * against these types. - */ +// The single cast from the JSON import, whose string unions TypeScript widens; PlatformsDataTest validates the file. export const PLATFORMS = data as PlatformsData; -/** The phrases of the `platforms` catalog namespace this module needs. */ export interface PlatformStrings { requirement: { named: string; unnamed: string }; systemsJoiner: string; @@ -115,7 +157,7 @@ export function isReleased(platform: Platform): platform is ReleasedPlatform { return platform.status === 'released'; } -/** Every platform with something to install, in data order. */ +// Every platform with something to install, in data order. export function releasedPlatforms(): ReleasedPlatform[] { return PLATFORMS.platforms.filter(isReleased); } @@ -128,6 +170,11 @@ export function iosPlatform(): IosPlatform { return find('ios') as IosPlatform; } +// Linux's record whether or not it has shipped; ask `isReleased` before showing it. +export function linuxPlatform(): LinuxPlatform { + return find('linux') as LinuxPlatform; +} + function find(id: PlatformId): Platform { const platform = PLATFORMS.platforms.find((entry) => entry.id === id); @@ -153,7 +200,7 @@ export function appStoreUrl(): string { } /** `macOS 13 Ventura or later`, `iOS and iPadOS 18 or later`, from the catalog's templates. */ -export function requirementText(platform: ReleasedPlatform, strings: PlatformStrings): string { +export function requirementText(platform: ApplePlatform, strings: PlatformStrings): string { const { systems, displayVersion, releaseName } = platform.requirements; const values = { systems: systems.join(strings.systemsJoiner), diff --git a/tests/Feature/Data/PlatformsDataTest.php b/tests/Feature/Data/PlatformsDataTest.php index 40a4388d..a1f641a5 100644 --- a/tests/Feature/Data/PlatformsDataTest.php +++ b/tests/Feature/Data/PlatformsDataTest.php @@ -28,12 +28,19 @@ function platformsEntry(string $id): array it('gives a released platform everything a page needs and an unreleased one nothing', function (): void { foreach (platformsJson()['platforms'] as $platform) { - if ($platform['status'] !== 'released') { + // Linux carries its record before it ships, so the pages can be built against it; its status alone shows it. + if ($platform['status'] !== 'released' && $platform['id'] !== 'linux') { expect(array_keys($platform))->toBe(['id', 'status'], "{$platform['id']} is not released, so it carries nothing else"); continue; } + if ($platform['id'] === 'linux') { + expect($platform)->toHaveKeys(['channel', 'deviceNames', 'page', 'distributions', 'architectures', 'destinations', 'release', 'appLanguages']); + + continue; + } + expect($platform)->toHaveKeys(['deviceNames', 'page', 'requirements', 'destinations', 'release', 'appLanguages']) ->and($platform['requirements']['minVersion'])->toMatch('/^\d+\.\d+$/') ->and($platform['release']['publishedAt'] <= now()->toDateString())->toBeTrue("{$platform['id']} is released in the future") @@ -73,3 +80,33 @@ function platformsEntry(string $id): array expect($code)->toMatch('/^[a-z]{2}$/'); } }); + +it('installs Linux from a repository on its channel, or from a release file of a known name', function (): void { + $linux = platformsEntry('linux'); + $destinations = collect($linux['destinations'])->keyBy('kind'); + $suffix = $linux['channel'] === 'beta' ? '-beta' : ''; + + expect($linux['channel'])->toBeIn(['beta', 'stable']) + ->and($linux['release']['version'])->toMatch('/^\d+\.\d+\.\d+(-beta\.\d+)?$/') + ->and($linux['architectures'])->toBe(['x86_64', 'arm64']) + ->and($destinations->keys()->all())->toBe(['ppa', 'copr', 'flatpak', 'packages']) + ->and($destinations['ppa']['repository'])->toBe("ppa:tablepro/tablepro{$suffix}") + ->and($destinations['copr']['repository'])->toBe("@tablepro/tablepro{$suffix}") + ->and($destinations['flatpak']['url'])->toStartWith('https://flatpak.tablepro.app/' . ($linux['channel'] === 'beta' ? 'beta-repo' : 'repo') . '/'); + + foreach (['ppa', 'copr', 'flatpak'] as $kind) { + expect($destinations[$kind]['channel'])->toBe($linux['channel']) + ->and($destinations[$kind]['commands'])->not->toBeEmpty(); + + foreach ($destinations[$kind]['commands'] as $command) { + expect($command)->toMatch('/^(sudo |flatpak )/')->not->toContain('$ '); + } + } + + expect(collect($linux['distributions'])->pluck('via')->all())->toBe(['ppa', 'copr', 'flatpak']); + + foreach ($destinations['packages']['assets'] as $asset) { + expect($asset['template'])->toContain('{version}')->toContain('{arch}') + ->and(array_keys($asset['arch']))->toBe($linux['architectures']); + } +}); From 6d6d7167cce26e25366ecbf034c349dc3fb9adfb Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 6 Nov 2026 22:37:30 +0700 Subject: [PATCH 2/5] feat(releases): read the newest linux-v release on its channel, and check the PPA, COPR and Flatpak once Linux ships --- .../Commands/RefreshLinuxReleaseCommand.php | 34 ++ app/Console/Commands/ReleaseCheckCommand.php | 104 ++++++- app/Services/Releases/LinuxRelease.php | 90 ++++++ app/Services/Releases/LinuxReleaseService.php | 291 ++++++++++++++++++ app/Services/Releases/PlatformCatalog.php | 69 +++++ routes/console.php | 7 +- .../Console/ReleaseCheckCommandTest.php | 51 +++ .../Services/LinuxReleaseServiceTest.php | 128 ++++++++ .../Services/MacReleaseServiceTest.php | 2 +- 9 files changed, 771 insertions(+), 5 deletions(-) create mode 100644 app/Console/Commands/RefreshLinuxReleaseCommand.php create mode 100644 app/Services/Releases/LinuxRelease.php create mode 100644 app/Services/Releases/LinuxReleaseService.php create mode 100644 tests/Feature/Services/LinuxReleaseServiceTest.php diff --git a/app/Console/Commands/RefreshLinuxReleaseCommand.php b/app/Console/Commands/RefreshLinuxReleaseCommand.php new file mode 100644 index 00000000..0c1ce33c --- /dev/null +++ b/app/Console/Commands/RefreshLinuxReleaseCommand.php @@ -0,0 +1,34 @@ +refresh(); + + if ($release !== null) { + $this->info("Linux release {$release->version} ({$release->source})."); + + return self::SUCCESS; + } + + if ($releases->failing()) { + $this->warn('GitHub has no Linux release on this channel yet, or did not answer. Serving the last good copy, or platforms.json.'); + + return self::FAILURE; + } + + $this->line('Another refresh is running. Nothing to do.'); + + return self::SUCCESS; + } +} diff --git a/app/Console/Commands/ReleaseCheckCommand.php b/app/Console/Commands/ReleaseCheckCommand.php index 53114880..fbe802c1 100644 --- a/app/Console/Commands/ReleaseCheckCommand.php +++ b/app/Console/Commands/ReleaseCheckCommand.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Services\Releases\LinuxReleaseService; use App\Services\Releases\MacReleaseService; use App\Services\Releases\PlatformCatalog; use App\Services\Releases\SparkleAppcast; @@ -32,7 +33,7 @@ * source cannot be read, because an unchecked fact is not a checked one. */ #[Signature('release:check')] -#[Description('Compare GitHub, the Sparkle appcast, Homebrew and the App Store with resources/data/platforms.json.')] +#[Description('Compare GitHub, the Sparkle appcast, Homebrew, the App Store and, once Linux ships, its PPA, COPR and Flatpak repository with resources/data/platforms.json.')] class ReleaseCheckCommand extends Command { private const TIMEOUT_SECONDS = 10; @@ -48,7 +49,7 @@ class ReleaseCheckCommand extends Command */ private array $rows = []; - public function handle(PlatformCatalog $platforms, MacReleaseService $releases, SparkleAppcast $appcast): int + public function handle(PlatformCatalog $platforms, MacReleaseService $releases, SparkleAppcast $appcast, LinuxReleaseService $linuxReleases): int { $mac = $platforms->find('mac'); $ios = $platforms->find('ios'); @@ -96,6 +97,11 @@ public function handle(PlatformCatalog $platforms, MacReleaseService $releases, is_array($de) ? ((int) ($de['resultCount'] ?? 0) > 0 ? 'yes' : 'no') : null, ); + // Before Linux ships no channel holds a package, so its rows would only report what is not there yet. + if ($platforms->isReleased('linux')) { + $this->checkLinux($platforms, $linuxReleases); + } + $this->table(['Channel', 'Field', 'platforms.json', 'Live', 'Result'], $this->rows); $failures = array_filter($this->rows, fn(array $row): bool => in_array($row[4], [self::DRIFT, self::UNREADABLE], true)); @@ -118,6 +124,100 @@ public function handle(PlatformCatalog $platforms, MacReleaseService $releases, * trailing GitHub is expected, and what matters is that `floorVersion` * says so, which the floor row checks. */ + private function checkLinux(PlatformCatalog $platforms, LinuxReleaseService $releases): void + { + $linux = $platforms->find('linux') ?? []; + $expected = $this->string($linux['release']['version'] ?? null); + + $github = $releases->fromGitHub(); + $this->compare('GitHub linux-v releases', 'version', $expected, $github?->version); + $this->compare('GitHub linux-v releases', 'published', $this->string($linux['release']['publishedAt'] ?? null), $github?->publishedAt); + + $ppa = $platforms->destination('linux', 'ppa'); + $this->compare('Launchpad PPA', 'version', $expected, is_array($ppa) ? $this->ppaVersion((string) $ppa['repository']) : null); + + $copr = $platforms->destination('linux', 'copr'); + $this->compare('COPR', 'version', $expected, is_array($copr) ? $this->coprVersion((string) $copr['repository']) : null); + + $flatpak = $platforms->destination('linux', 'flatpak'); + $this->compare('Flatpak repository', 'flatpakref reachable', 'yes', is_array($flatpak) ? $this->reachable((string) $flatpak['url']) : null); + } + + // `ppa:tablepro/tablepro-beta` publishes `0.1.0~beta.1-0ppa1~26.04`; the upstream part is the release. + private function ppaVersion(string $repository): ?string + { + if (preg_match('#^ppa:([a-z0-9.+-]+)/([a-z0-9.+-]+)$#', $repository, $matches) !== 1) { + return null; + } + + try { + $response = Http::timeout(self::TIMEOUT_SECONDS)->get("https://api.launchpad.net/1.0/~{$matches[1]}/+archive/ubuntu/{$matches[2]}", [ + 'ws.op' => 'getPublishedSources', + 'source_name' => 'tablepro', + 'status' => 'Published', + ]); + } catch (\Throwable) { + return null; + } + + $versions = array_filter(array_map(fn(mixed $entry): ?string => is_array($entry) ? $this->upstream($entry['source_package_version'] ?? null) : null, $response->json('entries') ?? [])); + + return $response->ok() ? $this->highest($versions) : null; + } + + // `@tablepro/tablepro-beta` builds `0.1.0~beta.1-1`; the upstream part is the release. + private function coprVersion(string $repository): ?string + { + if (preg_match('#^(@?[a-z0-9_.-]+)/([a-z0-9_.-]+)$#', $repository, $matches) !== 1) { + return null; + } + + try { + $response = Http::timeout(self::TIMEOUT_SECONDS)->get('https://copr.fedorainfracloud.org/api_3/package', [ + 'ownername' => $matches[1], + 'projectname' => $matches[2], + 'packagename' => 'tablepro', + 'with_latest_succeeded_build' => 'True', + ]); + } catch (\Throwable) { + return null; + } + + return $response->ok() ? $this->upstream($response->json('builds.latest_succeeded.source_package.version')) : null; + } + + private function reachable(string $url): ?string + { + try { + return Http::timeout(self::TIMEOUT_SECONDS)->head($url)->successful() ? 'yes' : 'no'; + } catch (\Throwable) { + return null; + } + } + + private function upstream(mixed $packageVersion): ?string + { + if (! is_string($packageVersion) || preg_match('/^(?:\d+:)?(\d+\.\d+\.\d+(?:~beta\.\d+)?)/', $packageVersion, $matches) !== 1) { + return null; + } + + return str_replace('~', '-', $matches[1]); + } + + /** + * @param array $versions + */ + private function highest(array $versions): ?string + { + if ($versions === []) { + return null; + } + + usort($versions, fn(string $a, string $b): int => version_compare($b, $a)); + + return $versions[0]; + } + private function compare(string $channel, string $field, ?string $expected, ?string $live, bool $drift = true, bool $readable = true): void { if (! $readable || $live === null) { diff --git a/app/Services/Releases/LinuxRelease.php b/app/Services/Releases/LinuxRelease.php new file mode 100644 index 00000000..40f74692 --- /dev/null +++ b/app/Services/Releases/LinuxRelease.php @@ -0,0 +1,90 @@ + $assets + */ + public function __construct( + public string $version, + public ?string $publishedAt, + public bool $prerelease, + public array $assets, + public ?string $checksumsUrl, + public string $releaseUrl, + public string $releasesUrl, + public string $source, + ) {} + + /** + * @param array $data + */ + public static function fromArray(array $data): ?self + { + if (! is_string($data['version'] ?? null) || ! is_string($data['releaseUrl'] ?? null) || ! is_string($data['releasesUrl'] ?? null) || ! is_string($data['source'] ?? null)) { + return null; + } + + $assets = []; + + foreach (is_array($data['assets'] ?? null) ? $data['assets'] : [] as $asset) { + if (is_array($asset) && is_string($asset['name'] ?? null) && is_string($asset['url'] ?? null)) { + $assets[] = [ + 'name' => $asset['name'], + 'url' => $asset['url'], + 'bytes' => is_int($asset['bytes'] ?? null) ? $asset['bytes'] : null, + 'sha256' => is_string($asset['sha256'] ?? null) ? $asset['sha256'] : null, + ]; + } + } + + return new self( + version: $data['version'], + publishedAt: is_string($data['publishedAt'] ?? null) ? $data['publishedAt'] : null, + prerelease: ($data['prerelease'] ?? false) === true, + assets: $assets, + checksumsUrl: is_string($data['checksumsUrl'] ?? null) ? $data['checksumsUrl'] : null, + releaseUrl: $data['releaseUrl'], + releasesUrl: $data['releasesUrl'], + source: $data['source'], + ); + } + + /** + * @return array{version: string, publishedAt: string|null, prerelease: bool, assets: list, checksumsUrl: string|null, releaseUrl: string, releasesUrl: string, source: string} + */ + public function toArray(): array + { + return [ + 'version' => $this->version, + 'publishedAt' => $this->publishedAt, + 'prerelease' => $this->prerelease, + 'assets' => $this->assets, + 'checksumsUrl' => $this->checksumsUrl, + 'releaseUrl' => $this->releaseUrl, + 'releasesUrl' => $this->releasesUrl, + 'source' => $this->source, + ]; + } + + /** + * The date is written in the page's language here, so the server render and the browser agree. + * + * @return array + */ + public function toProps(string $locale): array + { + return [ + ...$this->toArray(), + 'publishedOn' => $this->publishedAt === null ? null : Carbon::parse($this->publishedAt)->locale($locale)->isoFormat('LL'), + ]; + } +} diff --git a/app/Services/Releases/LinuxReleaseService.php b/app/Services/Releases/LinuxReleaseService.php new file mode 100644 index 00000000..b66e1f27 --- /dev/null +++ b/app/Services/Releases/LinuxReleaseService.php @@ -0,0 +1,291 @@ +refreshAfterResponse(); + + return $this->lastGood() ?? $this->fromData(); + } + + public function refresh(): ?LinuxRelease + { + $lock = Cache::lock(self::REFRESH_LOCK, self::REFRESH_LOCK_SECONDS); + + if (! $lock->get()) { + return null; + } + + try { + $release = $this->fromGitHub(); + + if ($release === null) { + Cache::put(self::FAILURE_KEY, true, self::FAILURE_SECONDS); + + return null; + } + + Cache::put(self::CACHE_KEY, $release->toArray(), self::FRESH_SECONDS); + Cache::forget(self::FAILURE_KEY); + $this->keepLastGood($release); + + return $release; + } finally { + $lock->release(); + } + } + + public function failing(): bool + { + return Cache::has(self::FAILURE_KEY); + } + + public function fromGitHub(): ?LinuxRelease + { + $packages = $this->platforms->linuxPackages(); + + if ($packages === null) { + return null; + } + + try { + $response = Http::timeout(self::TIMEOUT_SECONDS) + ->withHeaders(['Accept' => 'application/vnd.github+json', 'X-GitHub-Api-Version' => '2022-11-28']) + ->get('https://api.github.com/repos/' . $this->repo() . '/releases', ['per_page' => 30]); + } catch (\Throwable) { + return null; + } + + if (! $response->ok() || ! is_array($response->json()) || ! array_is_list($response->json())) { + return null; + } + + $stableOnly = $this->platforms->linuxChannel() === 'stable'; + + foreach ($response->json() as $release) { + if (! is_array($release) || ($release['draft'] ?? false) === true || ($stableOnly && ($release['prerelease'] ?? false) === true)) { + continue; + } + + $version = $this->versionFromTag((string) ($release['tag_name'] ?? ''), $packages['tagPrefix']); + + if ($version === null) { + continue; + } + + $assets = $this->assets($release['assets'] ?? [], $this->platforms->linuxAssetNames($version)); + + if ($assets === []) { + continue; + } + + $htmlUrl = $release['html_url'] ?? null; + + return new LinuxRelease( + version: $version, + publishedAt: $this->date($release['published_at'] ?? null), + prerelease: ($release['prerelease'] ?? false) === true, + assets: $assets, + checksumsUrl: $this->checksumsUrl($release['assets'] ?? [], $packages['checksums']), + releaseUrl: is_string($htmlUrl) && str_starts_with($htmlUrl, 'https://') ? $htmlUrl : ($this->platforms->linuxTagUrl($version) ?? $this->releasesUrl()), + releasesUrl: $this->releasesUrl(), + source: LinuxRelease::SOURCE_GITHUB, + ); + } + + return null; + } + + public function fromData(): ?LinuxRelease + { + $release = $this->platforms->linuxDataRelease(); + $packages = $this->platforms->linuxPackages(); + + if ($release === null || $packages === null) { + return null; + } + + $version = $release['version']; + $base = 'https://github.com/' . $this->repo() . '/releases/download/' . $packages['tagPrefix'] . $version . '/'; + + return new LinuxRelease( + version: $version, + publishedAt: $release['publishedAt'], + prerelease: str_contains($version, '-'), + assets: array_map(fn(string $name): array => ['name' => $name, 'url' => $base . rawurlencode($name), 'bytes' => null, 'sha256' => null], $this->platforms->linuxAssetNames($version)), + checksumsUrl: $base . rawurlencode($packages['checksums']), + releaseUrl: $this->platforms->linuxTagUrl($version) ?? $this->releasesUrl(), + releasesUrl: $this->releasesUrl(), + source: LinuxRelease::SOURCE_DATA, + ); + } + + public function lastGood(): ?LinuxRelease + { + try { + $raw = Storage::disk(self::LAST_GOOD_DISK)->get(self::LAST_GOOD_FILE); + } catch (\Throwable) { + return null; + } + + $value = is_string($raw) ? json_decode($raw, true) : null; + + return is_array($value) ? LinuxRelease::fromArray($value) : null; + } + + private function refreshAfterResponse(): void + { + if ($this->failing()) { + return; + } + + defer(function (): void { + if (Cache::get(self::CACHE_KEY) === null && ! $this->failing()) { + $this->refresh(); + } + }, self::REFRESH_LOCK); + } + + private function keepLastGood(LinuxRelease $release): void + { + try { + $json = json_encode($release->toArray(), JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); + $disk = Storage::disk(self::LAST_GOOD_DISK); + + if ($disk->get(self::LAST_GOOD_FILE) !== $json) { + $disk->put(self::LAST_GOOD_FILE, $json); + } + } catch (\Throwable) { + return; + } + } + + private function versionFromTag(string $tag, string $prefix): ?string + { + $pattern = '/^' . preg_quote($prefix, '/') . '(\d+\.\d+\.\d+(?:-beta\.\d+)?)$/'; + + return preg_match($pattern, $tag, $matches) === 1 ? $matches[1] : null; + } + + /** + * @param list $names + * @return list + */ + private function assets(mixed $assets, array $names): array + { + if (! is_array($assets)) { + return []; + } + + $byName = []; + + foreach ($assets as $asset) { + if (! is_array($asset) || ! is_string($asset['name'] ?? null) || ($asset['state'] ?? 'uploaded') !== 'uploaded') { + continue; + } + + $url = $asset['browser_download_url'] ?? null; + + if (! is_string($url) || ! str_starts_with($url, 'https://')) { + continue; + } + + $bytes = $asset['size'] ?? null; + $digest = $asset['digest'] ?? null; + + $byName[$asset['name']] = [ + 'name' => $asset['name'], + 'url' => $url, + 'bytes' => is_int($bytes) && $bytes > 0 ? $bytes : null, + 'sha256' => is_string($digest) && preg_match('/^sha256:([0-9a-f]{64})$/', $digest, $matches) === 1 ? $matches[1] : null, + ]; + } + + $found = []; + + foreach ($names as $name) { + if (isset($byName[$name])) { + $found[] = $byName[$name]; + } + } + + return $found; + } + + private function checksumsUrl(mixed $assets, string $name): ?string + { + foreach (is_array($assets) ? $assets : [] as $asset) { + if (is_array($asset) && ($asset['name'] ?? null) === $name && is_string($asset['browser_download_url'] ?? null) && str_starts_with($asset['browser_download_url'], 'https://')) { + return $asset['browser_download_url']; + } + } + + return null; + } + + private function date(mixed $timestamp): ?string + { + if (! is_string($timestamp) || $timestamp === '') { + return null; + } + + try { + return Carbon::parse($timestamp)->utc()->toDateString(); + } catch (\Throwable) { + return null; + } + } + + private function repo(): string + { + return (string) config('services.github.repo'); + } + + private function releasesUrl(): string + { + $url = $this->platforms->linuxPackages()['url'] ?? null; + + return is_string($url) && str_starts_with($url, 'https://') ? $url : 'https://github.com/' . $this->repo() . '/releases'; + } +} diff --git a/app/Services/Releases/PlatformCatalog.php b/app/Services/Releases/PlatformCatalog.php index cfe3647c..3395453d 100644 --- a/app/Services/Releases/PlatformCatalog.php +++ b/app/Services/Releases/PlatformCatalog.php @@ -163,6 +163,75 @@ public function macFloorVersion(): ?string * * @return array{deviceNames: list, requirements: array{systems: list, minVersion: string, displayVersion: string, releaseName: string|null}}|null */ + /** + * The Linux packages destination: GitHub release file templates, the tag prefix and how to verify a file. + * + * @return array{url: string, tagPrefix: string, tagTemplate: string, assets: list, arch: array}>, checksums: string, verify: string}|null + */ + public function linuxPackages(): ?array + { + $packages = $this->destination('linux', 'packages'); + + if ($packages === null || ! is_string($packages['tagPrefix'] ?? null) || ! is_array($packages['assets'] ?? null)) { + return null; + } + + return $packages; + } + + public function linuxChannel(): string + { + return ($this->find('linux')['channel'] ?? null) === 'stable' ? 'stable' : 'beta'; + } + + /** + * Every file a Linux release carries for each format, series and architecture, in data order. + * + * @return list + */ + public function linuxAssetNames(string $version): array + { + $names = []; + + foreach ($this->linuxPackages()['assets'] ?? [] as $asset) { + $series = $asset['series'] === [] ? [null] : $asset['series']; + + foreach ($series as $one) { + foreach ($asset['arch'] as $arch) { + $names[] = strtr($asset['template'], ['{version}' => $version, '{series}' => (string) $one, '{arch}' => $arch]); + } + } + } + + return $names; + } + + public function linuxTagUrl(string $version): ?string + { + $template = $this->linuxPackages()['tagTemplate'] ?? null; + + return is_string($template) && str_starts_with($template, 'https://') ? strtr($template, ['{version}' => $version]) : null; + } + + /** + * The release platforms.json records, for the page to show until GitHub answers. + * + * @return array{version: string, publishedAt: string|null}|null + */ + public function linuxDataRelease(): ?array + { + $release = $this->find('linux')['release'] ?? null; + + if (! is_array($release) || ! is_string($release['version'] ?? null)) { + return null; + } + + return [ + 'version' => $release['version'], + 'publishedAt' => is_string($release['publishedAt'] ?? null) ? $release['publishedAt'] : null, + ]; + } + public function summary(string $id): ?array { $platform = $this->find($id); diff --git a/routes/console.php b/routes/console.php index fdba57ab..433efe16 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,6 +1,7 @@ everyFifteenMinutes(); +Schedule::command(RefreshLinuxReleaseCommand::class)->everyFifteenMinutes(); + Schedule::command(RefreshStarCountCommand::class)->hourly(); diff --git a/tests/Feature/Console/ReleaseCheckCommandTest.php b/tests/Feature/Console/ReleaseCheckCommandTest.php index 3ecf9840..0647fb14 100644 --- a/tests/Feature/Console/ReleaseCheckCommandTest.php +++ b/tests/Feature/Console/ReleaseCheckCommandTest.php @@ -1,6 +1,7 @@ str_contains($request->url(), 'releases/latest')); }); + +function releasedLinuxFixture(): array +{ + $linux = collect(json_decode(File::get(resource_path('data/platforms.json')), true)['platforms'])->firstWhere('id', 'linux'); + + return ['linux' => [...$linux, 'status' => 'released', 'release' => ['version' => '0.1.0-beta.1', 'build' => null, 'publishedAt' => '2026-11-06']]]; +} + +function fakeLinuxChannels(string $ppa = '0.1.0~beta.1-0ppa1~26.04', string $copr = '0.1.0~beta.1-1'): void +{ + Http::fake([ + 'api.github.com/repos/TableProApp/TablePro/releases?*' => Http::response([[ + 'tag_name' => 'linux-v0.1.0-beta.1', + 'draft' => false, + 'prerelease' => true, + 'published_at' => '2026-11-06T10:00:00Z', + 'assets' => [['name' => 'tablepro-0.1.0-beta.1-x86_64.flatpak', 'state' => 'uploaded', 'size' => 1, 'browser_download_url' => 'https://github.com/TableProApp/TablePro/releases/download/linux-v0.1.0-beta.1/tablepro-0.1.0-beta.1-x86_64.flatpak']], + ]]), + 'api.launchpad.net/1.0/~tablepro/+archive/ubuntu/tablepro-beta*' => Http::response(['entries' => [['source_package_version' => $ppa]]]), + 'copr.fedorainfracloud.org/api_3/package*' => Http::response(['builds' => ['latest_succeeded' => ['source_package' => ['version' => $copr]]]]), + 'flatpak.tablepro.app/*' => Http::response('', 200), + ]); + fakeReleaseChannels(); +} + +it('leaves Linux out until it ships', function (): void { + fakeReleaseChannels(); + + $this->artisan('release:check')->doesntExpectOutputToContain('Launchpad PPA')->assertSuccessful(); +}); + +it('checks the Linux release on GitHub, the PPA, COPR and the Flatpak repository once Linux ships', function (): void { + bindReleaseFixturePlatforms(releasedLinuxFixture()); + fakeLinuxChannels(); + + $this->artisan('release:check') + ->expectsOutputToContain('GitHub linux-v releases') + ->expectsOutputToContain('Launchpad PPA') + ->expectsOutputToContain('COPR') + ->expectsOutputToContain('Flatpak repository') + ->doesntExpectOutputToContain('DRIFT') + ->assertSuccessful(); +}); + +it('reports a Linux channel that has moved past platforms.json', function (): void { + bindReleaseFixturePlatforms(releasedLinuxFixture()); + fakeLinuxChannels(copr: '0.1.0~beta.2-1'); + + $this->artisan('release:check')->expectsOutputToContain('DRIFT')->assertFailed(); +}); diff --git a/tests/Feature/Services/LinuxReleaseServiceTest.php b/tests/Feature/Services/LinuxReleaseServiceTest.php new file mode 100644 index 00000000..2eb5570d --- /dev/null +++ b/tests/Feature/Services/LinuxReleaseServiceTest.php @@ -0,0 +1,128 @@ +instance(PlatformCatalog::class, new PlatformCatalog()); +}); + +function linuxReleases(): LinuxReleaseService +{ + return app(LinuxReleaseService::class); +} + +function linuxAsset(string $name, ?string $digest = null): array +{ + return [ + 'name' => $name, + 'state' => 'uploaded', + 'size' => 31_000_000, + 'digest' => $digest, + 'browser_download_url' => "https://github.com/TableProApp/TablePro/releases/download/linux-v0.1.0-beta.2/{$name}", + ]; +} + +function linuxReleaseList(): array +{ + return [ + ['tag_name' => 'v0.86.0', 'draft' => false, 'prerelease' => false, 'published_at' => '2026-11-12T08:00:00Z', 'assets' => []], + ['tag_name' => 'linux-v0.2.0-beta.1', 'draft' => true, 'prerelease' => true, 'published_at' => null, 'assets' => [linuxAsset('tablepro-0.2.0-beta.1-x86_64.flatpak')]], + [ + 'tag_name' => 'linux-v0.1.0-beta.2', + 'draft' => false, + 'prerelease' => true, + 'published_at' => '2026-11-10T09:30:00Z', + 'html_url' => 'https://github.com/TableProApp/TablePro/releases/tag/linux-v0.1.0-beta.2', + 'assets' => [ + linuxAsset('tablepro-0.1.0-beta.2-ubuntu-26.04-amd64.deb', 'sha256:' . str_repeat('a', 64)), + linuxAsset('tablepro-0.1.0-beta.2-fedora-44-aarch64.rpm'), + linuxAsset('tablepro-0.1.0-beta.2-x86_64.flatpak'), + linuxAsset('SHA256SUMS'), + linuxAsset('tablepro.pot'), + ], + ], + ['tag_name' => 'linux-v0.1.0-beta.1', 'draft' => false, 'prerelease' => true, 'published_at' => '2026-11-06T10:00:00Z', 'assets' => [linuxAsset('tablepro-0.1.0-beta.1-x86_64.flatpak')]], + ]; +} + +it('takes the newest published linux-v release on the beta channel, with its packages, digests and checksums', function (): void { + Http::fake(['api.github.com/repos/*/releases*' => Http::response(linuxReleaseList())]); + + $release = linuxReleases()->refresh(); + + expect($release->source)->toBe(LinuxRelease::SOURCE_GITHUB) + ->and($release->version)->toBe('0.1.0-beta.2') + ->and($release->prerelease)->toBeTrue() + ->and($release->publishedAt)->toBe('2026-11-10') + ->and(array_column($release->assets, 'name'))->toBe([ + 'tablepro-0.1.0-beta.2-ubuntu-26.04-amd64.deb', + 'tablepro-0.1.0-beta.2-fedora-44-aarch64.rpm', + 'tablepro-0.1.0-beta.2-x86_64.flatpak', + ]) + ->and($release->assets[0]['sha256'])->toBe(str_repeat('a', 64)) + ->and($release->assets[1]['sha256'])->toBeNull() + ->and($release->checksumsUrl)->toEndWith('/linux-v0.1.0-beta.2/SHA256SUMS') + ->and(linuxReleases()->latest()->version)->toBe('0.1.0-beta.2'); +}); + +it('skips pre-releases once the channel is stable', function (): void { + $data = json_decode(File::get(resource_path('data/platforms.json')), true); + $data['platforms'] = array_map(fn(array $platform): array => $platform['id'] === 'linux' ? [...$platform, 'channel' => 'stable'] : $platform, $data['platforms']); + $path = storage_path('framework/testing/platforms-stable-' . uniqid() . '.json'); + File::ensureDirectoryExists(dirname($path)); + File::put($path, json_encode($data)); + app()->instance(PlatformCatalog::class, new PlatformCatalog($path)); + + $list = linuxReleaseList(); + $list[] = ['tag_name' => 'linux-v0.1.0', 'draft' => false, 'prerelease' => false, 'published_at' => '2026-12-01T09:00:00Z', 'assets' => [linuxAsset('tablepro-0.1.0-x86_64.flatpak')]]; + Http::fake(['api.github.com/repos/*/releases*' => Http::response($list)]); + + expect(linuxReleases()->refresh()->version)->toBe('0.1.0'); + + File::delete($path); +}); + +it('stands in with the release platforms.json records until GitHub has one', function (): void { + Http::fake(['api.github.com/repos/*/releases*' => Http::response([['tag_name' => 'v0.85.0', 'draft' => false, 'prerelease' => false, 'assets' => []]])]); + + expect(linuxReleases()->refresh())->toBeNull() + ->and(linuxReleases()->failing())->toBeTrue(); + + $release = linuxReleases()->latest(); + $recorded = app(PlatformCatalog::class)->linuxDataRelease(); + + expect($release->source)->toBe(LinuxRelease::SOURCE_DATA) + ->and($release->version)->toBe($recorded['version']) + ->and($release->prerelease)->toBe(str_contains($recorded['version'], '-')) + ->and($release->assets)->not->toBeEmpty() + ->and(collect($release->assets)->every(fn(array $asset): bool => str_starts_with($asset['url'], "https://github.com/TableProApp/TablePro/releases/download/linux-v{$recorded['version']}/") && $asset['sha256'] === null))->toBeTrue(); +}); + +it('names every package file of a release for each series and architecture', function (): void { + $names = app(PlatformCatalog::class)->linuxAssetNames('0.1.0-beta.1'); + + expect($names)->toContain( + 'tablepro-0.1.0-beta.1-ubuntu-26.04-amd64.deb', + 'tablepro-0.1.0-beta.1-ubuntu-26.10-arm64.deb', + 'tablepro-0.1.0-beta.1-fedora-45-aarch64.rpm', + 'tablepro-0.1.0-beta.1-x86_64.flatpak', + 'tablepro-0.1.0-beta.1-aarch64.flatpak', + )->and($names)->toBe(array_values(array_unique($names))); +}); + +it('refreshes the Linux release on the same schedule as the Mac one', function (): void { + $events = collect(app(Schedule::class)->events())->filter(fn(Event $event): bool => str_contains((string) $event->command, 'release:refresh-linux')); + + expect($events)->toHaveCount(1) + ->and($events->first()->expression)->toBe('*/15 * * * *'); +}); diff --git a/tests/Feature/Services/MacReleaseServiceTest.php b/tests/Feature/Services/MacReleaseServiceTest.php index cac3a7f5..6cac250e 100644 --- a/tests/Feature/Services/MacReleaseServiceTest.php +++ b/tests/Feature/Services/MacReleaseServiceTest.php @@ -402,7 +402,7 @@ function githubApiCalls(): int it('refreshes from the scheduler every 15 minutes', function (): void { $events = collect(app(Schedule::class)->events()) - ->filter(fn(Event $event): bool => str_contains((string) $event->command, 'release:refresh')); + ->filter(fn(Event $event): bool => preg_match('/release:refresh$/', (string) $event->command) === 1); expect($events)->toHaveCount(1) ->and($events->first()->expression)->toBe('*/15 * * * *'); From 4f45acc81dde99415811c62946914f15da25b1eb Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 6 Nov 2026 22:37:31 +0700 Subject: [PATCH 3/5] feat(data): where each engine and feature works on Linux, with the app repo as evidence --- app/Support/Features/FeatureFacts.php | 3 + .../data/content/de/features/ai-mcp.json | 28 ++++++--- .../data/content/de/features/connections.json | 50 ++++++++++----- .../content/de/features/data-editing.json | 32 +++++++--- .../content/de/features/import-export.json | 29 ++++++--- .../data/content/de/features/querying.json | 32 +++++++--- .../data/content/de/features/schema.json | 28 ++++++--- .../content/de/features/sync-and-teams.json | 27 +++++--- .../data/content/en/features/ai-mcp.json | 16 ++--- .../data/content/en/features/connections.json | 30 ++++----- .../content/en/features/data-editing.json | 18 +++--- .../content/en/features/import-export.json | 18 +++--- .../data/content/en/features/querying.json | 20 +++--- .../data/content/en/features/schema.json | 18 +++--- .../content/en/features/sync-and-teams.json | 18 +++--- .../data/content/es/features/ai-mcp.json | 28 ++++++--- .../data/content/es/features/connections.json | 50 ++++++++++----- .../content/es/features/data-editing.json | 32 +++++++--- .../content/es/features/import-export.json | 29 ++++++--- .../data/content/es/features/querying.json | 32 +++++++--- .../data/content/es/features/schema.json | 28 ++++++--- .../content/es/features/sync-and-teams.json | 27 +++++--- .../data/content/fr/features/ai-mcp.json | 28 ++++++--- .../data/content/fr/features/connections.json | 50 ++++++++++----- .../content/fr/features/data-editing.json | 32 +++++++--- .../content/fr/features/import-export.json | 29 ++++++--- .../data/content/fr/features/querying.json | 32 +++++++--- .../data/content/fr/features/schema.json | 28 ++++++--- .../content/fr/features/sync-and-teams.json | 27 +++++--- .../data/content/id/features/ai-mcp.json | 28 ++++++--- .../data/content/id/features/connections.json | 50 ++++++++++----- .../content/id/features/data-editing.json | 32 +++++++--- .../content/id/features/import-export.json | 29 ++++++--- .../data/content/id/features/querying.json | 32 +++++++--- .../data/content/id/features/schema.json | 28 ++++++--- .../content/id/features/sync-and-teams.json | 27 +++++--- .../data/content/it/features/ai-mcp.json | 28 ++++++--- .../data/content/it/features/connections.json | 50 ++++++++++----- .../content/it/features/data-editing.json | 32 +++++++--- .../content/it/features/import-export.json | 29 ++++++--- .../data/content/it/features/querying.json | 32 +++++++--- .../data/content/it/features/schema.json | 28 ++++++--- .../content/it/features/sync-and-teams.json | 27 +++++--- .../data/content/ja/features/ai-mcp.json | 28 ++++++--- .../data/content/ja/features/connections.json | 50 ++++++++++----- .../content/ja/features/data-editing.json | 32 +++++++--- .../content/ja/features/import-export.json | 29 ++++++--- .../data/content/ja/features/querying.json | 32 +++++++--- .../data/content/ja/features/schema.json | 28 ++++++--- .../content/ja/features/sync-and-teams.json | 27 +++++--- .../data/content/ko/features/ai-mcp.json | 28 ++++++--- .../data/content/ko/features/connections.json | 50 ++++++++++----- .../content/ko/features/data-editing.json | 32 +++++++--- .../content/ko/features/import-export.json | 29 ++++++--- .../data/content/ko/features/querying.json | 32 +++++++--- .../data/content/ko/features/schema.json | 28 ++++++--- .../content/ko/features/sync-and-teams.json | 27 +++++--- .../data/content/pt-BR/features/ai-mcp.json | 28 ++++++--- .../content/pt-BR/features/connections.json | 50 ++++++++++----- .../content/pt-BR/features/data-editing.json | 32 +++++++--- .../content/pt-BR/features/import-export.json | 29 ++++++--- .../data/content/pt-BR/features/querying.json | 32 +++++++--- .../data/content/pt-BR/features/schema.json | 28 ++++++--- .../pt-BR/features/sync-and-teams.json | 27 +++++--- .../data/content/vi/features/ai-mcp.json | 28 ++++++--- .../data/content/vi/features/connections.json | 50 ++++++++++----- .../content/vi/features/data-editing.json | 32 +++++++--- .../content/vi/features/import-export.json | 29 ++++++--- .../data/content/vi/features/querying.json | 32 +++++++--- .../data/content/vi/features/schema.json | 28 ++++++--- .../content/vi/features/sync-and-teams.json | 27 +++++--- .../data/content/zh-Hans/features/ai-mcp.json | 28 ++++++--- .../content/zh-Hans/features/connections.json | 50 ++++++++++----- .../zh-Hans/features/data-editing.json | 32 +++++++--- .../zh-Hans/features/import-export.json | 29 ++++++--- .../content/zh-Hans/features/querying.json | 32 +++++++--- .../data/content/zh-Hans/features/schema.json | 28 ++++++--- .../zh-Hans/features/sync-and-teams.json | 27 +++++--- .../data/content/zh-Hant/features/ai-mcp.json | 28 ++++++--- .../content/zh-Hant/features/connections.json | 50 ++++++++++----- .../zh-Hant/features/data-editing.json | 32 +++++++--- .../zh-Hant/features/import-export.json | 29 ++++++--- .../content/zh-Hant/features/querying.json | 32 +++++++--- .../data/content/zh-Hant/features/schema.json | 28 ++++++--- .../zh-Hant/features/sync-and-teams.json | 27 +++++--- resources/data/engines.json | 37 +++++++++++ resources/data/facts.json | 61 +++++++++++++++++-- resources/js/components/features/README.md | 13 ++-- resources/js/components/features/types.ts | 6 ++ resources/js/lib/data/engines.ts | 11 ++++ resources/js/lib/data/facts.ts | 20 +++++- tests/Feature/Data/EnginesDataTest.php | 22 ++++++- tests/Feature/Data/FactsDataTest.php | 29 +++++++++ tests/Feature/Pages/FeaturesTest.php | 38 +++++++++++- 94 files changed, 2021 insertions(+), 843 deletions(-) diff --git a/app/Support/Features/FeatureFacts.php b/app/Support/Features/FeatureFacts.php index ed1c113e..429b7483 100644 --- a/app/Support/Features/FeatureFacts.php +++ b/app/Support/Features/FeatureFacts.php @@ -131,6 +131,7 @@ private function limits(array $facts, array $pricing): array 'explainNoise' => $limit('explainNoise'), 'filterOperatorCount' => ['kind' => 'number', 'value' => count($facts['filterOperators']['mac'] ?? [])], 'filterOperatorCountIos' => ['kind' => 'number', 'value' => count($facts['filterOperators']['ios'] ?? [])], + 'mcpToolCountLinux' => ['kind' => 'number', 'value' => count($facts['mcp']['linux']['tools'] ?? [])], 'deviceLimit' => ['kind' => 'number', 'value' => (int) ($pricing['license']['deviceLimit'] ?? 0)], ]; } @@ -154,7 +155,9 @@ private function lists(array $facts): array 'macSafeModeDefault' => $default('mac'), 'iosSafeModeLevels' => $names($levels('ios')), 'iosSafeModeDefault' => $default('ios'), + 'linuxSafeModeLevels' => $names($levels('linux')), 'aiProviders' => $names($facts['ai']['providers'] ?? []), + 'aiProvidersLinux' => $names($facts['ai']['linux']['providers'] ?? []), 'mcpHost' => ['kind' => 'text', 'value' => (string) ($facts['mcp']['host'] ?? '')], 'mcpPort' => ['kind' => 'text', 'value' => (string) ($facts['mcp']['defaultPort'] ?? '')], 'mcpSetupClients' => $names($facts['mcp']['clients']['setupSheet'] ?? []), diff --git a/resources/data/content/de/features/ai-mcp.json b/resources/data/content/de/features/ai-mcp.json index 1d431fd7..b7bf72f7 100644 --- a/resources/data/content/de/features/ai-mcp.json +++ b/resources/data/content/de/features/ai-mcp.json @@ -130,35 +130,47 @@ "availability": [ { "label": "KI-Chat mit eigenem Anbieter", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize und Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Inline-Vorschläge und AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Inline-Vorschläge; keine AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MCP-Server", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} Tools, nur über HTTP" }, { "label": "Externe MCP-Server", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript und Shell-Befehl tablepro", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Der Befehl tablepro öffnet Dateien" }, { "label": "Raycast-Erweiterung", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/de/features/connections.json b/resources/data/content/de/features/connections.json index 00add871..e4a1eebc 100644 --- a/resources/data/content/de/features/connections.json +++ b/resources/data/content/de/features/connections.json @@ -189,67 +189,87 @@ { "label": "Verbindungsformular, Gruppen, Tags und Favoriten", "ios": "partial", - "iosNote": "Ein Tag pro Verbindung; keine Farben" + "iosNote": "Ein Tag pro Verbindung; keine Farben", + "linux": "yes" }, { "label": "Import aus URL oder Zwischenablage", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Import aus anderen Apps, Projektordnern und AWS", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Nur aus anderen Apps" }, { "label": "SSH-Tunnel mit Passwort oder privatem Schlüssel", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "SSH-Agent, Einmalcodes und Jump-Hosts", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "TLS mit Zertifikatsprüfung", "ios": "partial", - "iosNote": "SQL Server verschlüsselt ohne Prüfung" + "iosNote": "SQL Server verschlüsselt ohne Prüfung", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy und Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM, Kerberos und Google-Anmeldung", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Nur Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Bei vom Mac synchronisierten Verbindungen" + "iosNote": "Bei vom Mac synchronisierten Verbindungen", + "linux": "no" }, { "label": "Passwörter im Schlüsselbund", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "Im Schlüsselbund des Systems" }, { "label": "Passwortquellen, Zugangsdatenprofile und ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Safe Mode pro Verbindung", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Verbindungs- und Abfragezeitlimits", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Nur Abfragezeitlimit" }, { "label": "Safe-Mode-Mindeststufe über ein Konfigurationsprofil", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/de/features/data-editing.json b/resources/data/content/de/features/data-editing.json index 51383d0d..eed2e503 100644 --- a/resources/data/content/de/features/data-editing.json +++ b/resources/data/content/de/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "Sortieren, Blättern und Filtern", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Gespeicherte Filter und Hervorhebungsregeln", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Fremdschlüsselnavigation und Werteauswahl", "ios": "partial", - "iosNote": "Tippen zeigt die referenzierte Zeile" + "iosNote": "Tippen zeigt die referenzierte Zeile", + "linux": "partial", + "linuxNote": "Navigation; keine Werteauswahl" }, { "label": "Zeilen bearbeiten, einfügen und löschen", "ios": "partial", - "iosNote": "Zeileneditor; benötigt einen Primärschlüssel" + "iosNote": "Zeileneditor; benötigt einen Primärschlüssel", + "linux": "partial", + "linuxNote": "Benötigt einen Primärschlüssel" }, { "label": "Vorgemerkte Änderungen, Preview SQL und Rückgängig", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Vorgemerkte Änderungen und Rückgängig; kein Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Touch ID oder Passwort vor Schreibvorgängen", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MongoDB-Dokumente und Redis-Schlüssel", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Redis-Schlüssel nur per Befehl änderbar" } ], "limits": [ diff --git a/resources/data/content/de/features/import-export.json b/resources/data/content/de/features/import-export.json index 1b355c23..b3c9e260 100644 --- a/resources/data/content/de/features/import-export.json +++ b/resources/data/content/de/features/import-export.json @@ -136,41 +136,52 @@ { "label": "Dateien in Tabellen importieren", "ios": "partial", - "iosNote": "Kurzbefehl-Aktionen, aus JSON oder CSV" + "iosNote": "Kurzbefehl-Aktionen, aus JSON oder CSV", + "linux": "partial", + "linuxNote": "CSV in eine bestehende Tabelle und SQL-Dateien" }, { "label": "Gespeicherte Zuordnungen, Match by Name und Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Tabellen und Ergebnisse in Dateien exportieren", "ios": "partial", - "iosNote": "Als JSON, CSV oder SQL INSERT kopieren oder teilen" + "iosNote": "Als JSON, CSV oder SQL INSERT kopieren oder teilen", + "linux": "yes" }, { "label": "{exportPluginFormats}-Export-Plugin", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Fenster Data Files", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump und Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "SQL-Dumps ohne externe Tools" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "SQLite- und DuckDB-Dateien", "ios": "partial", - "iosNote": "Dateien auf dem Gerät oder DuckDB im Arbeitsspeicher" + "iosNote": "Dateien auf dem Gerät oder DuckDB im Arbeitsspeicher", + "linux": "yes" }, { "label": "SQLite-Dateien über SSH", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/de/features/querying.json b/resources/data/content/de/features/querying.json index dc829c7d..8833b874 100644 --- a/resources/data/content/de/features/querying.json +++ b/resources/data/content/de/features/querying.json @@ -149,46 +149,58 @@ { "label": "SQL-Editor mit Autovervollständigung und Formatierung", "ios": "partial", - "iosNote": "Hervorhebung, Run und Stop; keine Autovervollständigung" + "iosNote": "Hervorhebung, Run und Stop; keine Autovervollständigung", + "linux": "yes" }, { "label": "Abfrageparameter", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Abfrageverlauf", "ios": "partial", - "iosNote": "Die letzten {historyEntriesIos} Abfragen auf dem Gerät" + "iosNote": "Die letzten {historyEntriesIos} Abfragen auf dem Gerät", + "linux": "yes" }, { "label": "Gespeicherte Abfragen und verknüpfte SQL-Ordner", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Nur gespeicherte Abfragen" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Suche nach Verbindungen und Tabellen in der Seitenleiste" }, { "label": "EXPLAIN und EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Kartenansicht", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Ergebnisse kopieren oder teilen", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/de/features/schema.json b/resources/data/content/de/features/schema.json index 195e30f8..9d75e143 100644 --- a/resources/data/content/de/features/schema.json +++ b/resources/data/content/de/features/schema.json @@ -135,40 +135,50 @@ { "label": "Strukturbearbeitung mit DDL-Vorschau", "ios": "partial", - "iosNote": "Spalten, Indizes und Fremdschlüssel, schreibgeschützt" + "iosNote": "Spalten, Indizes und Fremdschlüssel, schreibgeschützt", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Tabellen leeren und löschen", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "ER-Diagramm", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Routinen, Trigger und Tabellenordner", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Routinen und Trigger; keine Tabellenordner" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database und Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server-Dashboard und Tabellenwartung", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/de/features/sync-and-teams.json b/resources/data/content/de/features/sync-and-teams.json index 41f838e0..3270ebc9 100644 --- a/resources/data/content/de/features/sync-and-teams.json +++ b/resources/data/content/de/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "Verbindungen, Gruppen und Tags, kostenlos" + "iosNote": "Verbindungen, Gruppen und Tags, kostenlos", + "linux": "no" }, { "label": "Passwortsynchronisierung über den iCloud-Schlüsselbund", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Synchronisierung auswählen und Verbindungen Local only behalten", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Handoff mit aktiviertem iCloud Sync", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": ".tablepro-Dateien exportieren und importieren", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Encrypted Export mit Passwörtern", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/en/features/ai-mcp.json b/resources/data/content/en/features/ai-mcp.json index a2e2a57c..2169cc65 100644 --- a/resources/data/content/en/features/ai-mcp.json +++ b/resources/data/content/en/features/ai-mcp.json @@ -106,14 +106,14 @@ } ], "availability": [ - { "label": "AI chat with your own provider", "ios": "no" }, - { "label": "Review, Explain, Optimize and Fix with AI", "ios": "no" }, - { "label": "Inline suggestions and AI Rules", "ios": "no" }, - { "label": "Agent mode", "ios": "no" }, - { "label": "MCP server", "ios": "no" }, - { "label": "Outside MCP servers", "ios": "no" }, - { "label": "AppleScript and the tablepro shell command", "ios": "no" }, - { "label": "Raycast extension", "ios": "no" } + { "label": "AI chat with your own provider", "ios": "no", "linux": "partial", "linuxNote": "{aiProvidersLinux}" }, + { "label": "Review, Explain, Optimize and Fix with AI", "ios": "no", "linux": "yes" }, + { "label": "Inline suggestions and AI Rules", "ios": "no", "linux": "partial", "linuxNote": "Inline suggestions; no AI Rules" }, + { "label": "Agent mode", "ios": "no", "linux": "no" }, + { "label": "MCP server", "ios": "no", "linux": "partial", "linuxNote": "{mcpToolCountLinux} tools, over HTTP only" }, + { "label": "Outside MCP servers", "ios": "no", "linux": "no" }, + { "label": "AppleScript and the tablepro shell command", "ios": "no", "linux": "partial", "linuxNote": "The tablepro command opens files" }, + { "label": "Raycast extension", "ios": "no", "linux": "no" } ], "limits": [ "Cursor cannot call TablePro’s tools, so with Cursor as the provider, Edit and Agent run no queries. Local models call tools only if the model supports it.", diff --git a/resources/data/content/en/features/connections.json b/resources/data/content/en/features/connections.json index f79e5280..df186e7c 100644 --- a/resources/data/content/en/features/connections.json +++ b/resources/data/content/en/features/connections.json @@ -147,21 +147,21 @@ } ], "availability": [ - { "label": "Connection form, groups, tags and favorites", "ios": "partial", "iosNote": "One tag per connection; no colors" }, - { "label": "Import from a URL or the clipboard", "ios": "no" }, - { "label": "Import from other apps, project folders and AWS", "ios": "no" }, - { "label": "SSH tunnel with a password or private key", "ios": "yes" }, - { "label": "SSH agent, one-time codes and jump hosts", "ios": "no" }, - { "label": "TLS with certificate checks", "ios": "partial", "iosNote": "SQL Server encrypts without verifying" }, - { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy and Tunnel Command", "ios": "no" }, - { "label": "AWS IAM, Kerberos and Google sign-in", "ios": "no" }, - { "label": "Microsoft Entra ID", "ios": "partial", "iosNote": "On connections synced from a Mac" }, - { "label": "Passwords in the Keychain", "ios": "yes" }, - { "label": "Password sources, credential profiles and ~/.pgpass", "ios": "no" }, - { "label": "Environment Variables", "paid": "environment-variables", "ios": "no" }, - { "label": "Safe Mode per connection", "ios": "partial", "iosNote": "{iosSafeModeLevels}" }, - { "label": "Connect and query timeouts", "ios": "no" }, - { "label": "Minimum Safe Mode level through a configuration profile", "ios": "no" } + { "label": "Connection form, groups, tags and favorites", "ios": "partial", "iosNote": "One tag per connection; no colors", "linux": "yes" }, + { "label": "Import from a URL or the clipboard", "ios": "no", "linux": "yes" }, + { "label": "Import from other apps, project folders and AWS", "ios": "no", "linux": "partial", "linuxNote": "From other apps only" }, + { "label": "SSH tunnel with a password or private key", "ios": "yes", "linux": "yes" }, + { "label": "SSH agent, one-time codes and jump hosts", "ios": "no", "linux": "yes" }, + { "label": "TLS with certificate checks", "ios": "partial", "iosNote": "SQL Server encrypts without verifying", "linux": "yes" }, + { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy and Tunnel Command", "ios": "no", "linux": "no" }, + { "label": "AWS IAM, Kerberos and Google sign-in", "ios": "no", "linux": "partial", "linuxNote": "Kerberos only" }, + { "label": "Microsoft Entra ID", "ios": "partial", "iosNote": "On connections synced from a Mac", "linux": "no" }, + { "label": "Passwords in the Keychain", "ios": "yes", "linux": "partial", "linuxNote": "In the system keyring" }, + { "label": "Password sources, credential profiles and ~/.pgpass", "ios": "no", "linux": "no" }, + { "label": "Environment Variables", "paid": "environment-variables", "ios": "no", "linux": "no" }, + { "label": "Safe Mode per connection", "ios": "partial", "iosNote": "{iosSafeModeLevels}", "linux": "partial", "linuxNote": "{linuxSafeModeLevels}" }, + { "label": "Connect and query timeouts", "ios": "no", "linux": "partial", "linuxNote": "Query timeout only" }, + { "label": "Minimum Safe Mode level through a configuration profile", "ios": "no", "linux": "no" } ], "limits": [ "Password sources and the Network tab’s proxies and tunnel commands stay on the Mac where you set them: they never sync, so a synced connection needs them set up again on each Mac.", diff --git a/resources/data/content/en/features/data-editing.json b/resources/data/content/en/features/data-editing.json index 2396362a..41d31642 100644 --- a/resources/data/content/en/features/data-editing.json +++ b/resources/data/content/en/features/data-editing.json @@ -100,15 +100,15 @@ } ], "availability": [ - { "label": "Sorting, paging and filters", "ios": "yes" }, - { "label": "Saved filters and highlight rules", "ios": "no" }, - { "label": "Foreign key navigation and value picker", "ios": "partial", "iosNote": "Tap to preview the referenced row" }, - { "label": "Edit, insert and delete rows", "ios": "partial", "iosNote": "Row editor; needs a primary key" }, - { "label": "Staged edits, Preview SQL and undo", "ios": "no" }, - { "label": "Safe Mode", "ios": "partial", "iosNote": "{iosSafeModeLevels}" }, - { "label": "Touch ID or password before a write", "ios": "no" }, - { "label": "Data Rewind", "paid": "data-rewind", "ios": "no" }, - { "label": "MongoDB documents and Redis keys", "ios": "no" } + { "label": "Sorting, paging and filters", "ios": "yes", "linux": "yes" }, + { "label": "Saved filters and highlight rules", "ios": "no", "linux": "no" }, + { "label": "Foreign key navigation and value picker", "ios": "partial", "iosNote": "Tap to preview the referenced row", "linux": "partial", "linuxNote": "Navigation; no value picker" }, + { "label": "Edit, insert and delete rows", "ios": "partial", "iosNote": "Row editor; needs a primary key", "linux": "partial", "linuxNote": "Needs a primary key" }, + { "label": "Staged edits, Preview SQL and undo", "ios": "no", "linux": "partial", "linuxNote": "Staged edits and undo; no Preview SQL" }, + { "label": "Safe Mode", "ios": "partial", "iosNote": "{iosSafeModeLevels}", "linux": "partial", "linuxNote": "{linuxSafeModeLevels}" }, + { "label": "Touch ID or password before a write", "ios": "no", "linux": "no" }, + { "label": "Data Rewind", "paid": "data-rewind", "ios": "no", "linux": "no" }, + { "label": "MongoDB documents and Redis keys", "ios": "no", "linux": "partial", "linuxNote": "Redis keys change only through commands" } ], "limits": [ "Data Rewind is not a backup. It does not undo triggers, cascades or changes made outside TablePro, and it cannot restore rows without a primary key, values over {rewindMaxValue} MB or values the server computed.", diff --git a/resources/data/content/en/features/import-export.json b/resources/data/content/en/features/import-export.json index 06931a49..1bfc4232 100644 --- a/resources/data/content/en/features/import-export.json +++ b/resources/data/content/en/features/import-export.json @@ -107,15 +107,15 @@ } ], "availability": [ - { "label": "Import files into a table", "ios": "partial", "iosNote": "Shortcuts actions, from JSON or CSV" }, - { "label": "Saved mappings, Match by Name and Match by Position", "ios": "no" }, - { "label": "Export tables and results to files", "ios": "partial", "iosNote": "Copy or share as JSON, CSV or SQL INSERT" }, - { "label": "{exportPluginFormats} export plugin", "ios": "no" }, - { "label": "Data Files window", "ios": "no" }, - { "label": "Backup Dump and Restore Dump", "ios": "no" }, - { "label": "Server-Side Export", "ios": "no" }, - { "label": "SQLite and DuckDB files", "ios": "partial", "iosNote": "Files on the device, or DuckDB in memory" }, - { "label": "SQLite files over SSH", "ios": "no" } + { "label": "Import files into a table", "ios": "partial", "iosNote": "Shortcuts actions, from JSON or CSV", "linux": "partial", "linuxNote": "CSV into an existing table, and SQL files" }, + { "label": "Saved mappings, Match by Name and Match by Position", "ios": "no", "linux": "no" }, + { "label": "Export tables and results to files", "ios": "partial", "iosNote": "Copy or share as JSON, CSV or SQL INSERT", "linux": "yes" }, + { "label": "{exportPluginFormats} export plugin", "ios": "no", "linux": "no" }, + { "label": "Data Files window", "ios": "no", "linux": "no" }, + { "label": "Backup Dump and Restore Dump", "ios": "no", "linux": "partial", "linuxNote": "SQL dumps written without external tools" }, + { "label": "Server-Side Export", "ios": "no", "linux": "no" }, + { "label": "SQLite and DuckDB files", "ios": "partial", "iosNote": "Files on the device, or DuckDB in memory", "linux": "yes" }, + { "label": "SQLite files over SSH", "ios": "no", "linux": "no" } ], "limits": [ "A SQL export includes DROP TABLE IF EXISTS before each table unless you turn that off, so check the options before running the file against a database whose tables you want to keep.", diff --git a/resources/data/content/en/features/querying.json b/resources/data/content/en/features/querying.json index 9f7a5871..6b96eda7 100644 --- a/resources/data/content/en/features/querying.json +++ b/resources/data/content/en/features/querying.json @@ -118,16 +118,16 @@ } ], "availability": [ - { "label": "SQL editor with autocomplete and formatting", "ios": "partial", "iosNote": "Highlighting, Run and Stop; no autocomplete" }, - { "label": "Query parameters", "ios": "no" }, - { "label": "Query history", "ios": "partial", "iosNote": "The last {historyEntriesIos} queries on the device" }, - { "label": "Saved queries and linked SQL folders", "ios": "no" }, - { "label": "Open Quickly", "ios": "no" }, - { "label": "EXPLAIN and EXPLAIN Compare", "ios": "no" }, - { "label": "Query Insights", "paid": "query-insights", "ios": "no" }, - { "label": "Result Charts", "paid": "result-charts", "ios": "no" }, - { "label": "Map view", "ios": "no" }, - { "label": "Copy or share results", "ios": "yes" } + { "label": "SQL editor with autocomplete and formatting", "ios": "partial", "iosNote": "Highlighting, Run and Stop; no autocomplete", "linux": "yes" }, + { "label": "Query parameters", "ios": "no", "linux": "no" }, + { "label": "Query history", "ios": "partial", "iosNote": "The last {historyEntriesIos} queries on the device", "linux": "yes" }, + { "label": "Saved queries and linked SQL folders", "ios": "no", "linux": "partial", "linuxNote": "Saved queries only" }, + { "label": "Open Quickly", "ios": "no", "linux": "partial", "linuxNote": "Sidebar search of connections and tables" }, + { "label": "EXPLAIN and EXPLAIN Compare", "ios": "no", "linux": "no" }, + { "label": "Query Insights", "paid": "query-insights", "ios": "no", "linux": "no" }, + { "label": "Result Charts", "paid": "result-charts", "ios": "no", "linux": "no" }, + { "label": "Map view", "ios": "no", "linux": "no" }, + { "label": "Copy or share results", "ios": "yes", "linux": "yes" } ], "limits": [ "A query returns at most {rowCap} rows unless you raise the cap in Settings, up to {rowCapMax}, or run it with ⌘⌥⏎. A LIMIT you write yourself always wins.", diff --git a/resources/data/content/en/features/schema.json b/resources/data/content/en/features/schema.json index 0ba1fb0c..90877f00 100644 --- a/resources/data/content/en/features/schema.json +++ b/resources/data/content/en/features/schema.json @@ -109,15 +109,15 @@ } ], "availability": [ - { "label": "Structure editing with a DDL preview", "ios": "partial", "iosNote": "Columns, indexes and foreign keys, read-only" }, - { "label": "New Table", "ios": "no" }, - { "label": "Truncate and drop tables", "ios": "yes" }, - { "label": "ER diagram", "ios": "no" }, - { "label": "Routines, triggers and table folders", "ios": "no" }, - { "label": "Compare & Sync", "paid": "compare-sync", "ios": "no" }, - { "label": "Copy To, Duplicate Database and Transfer To", "ios": "no" }, - { "label": "Users & Roles", "ios": "no" }, - { "label": "Server Dashboard and table maintenance", "ios": "no" } + { "label": "Structure editing with a DDL preview", "ios": "partial", "iosNote": "Columns, indexes and foreign keys, read-only", "linux": "yes" }, + { "label": "New Table", "ios": "no", "linux": "yes" }, + { "label": "Truncate and drop tables", "ios": "yes", "linux": "yes" }, + { "label": "ER diagram", "ios": "no", "linux": "yes" }, + { "label": "Routines, triggers and table folders", "ios": "no", "linux": "partial", "linuxNote": "Routines and triggers; no table folders" }, + { "label": "Compare & Sync", "paid": "compare-sync", "ios": "no", "linux": "no" }, + { "label": "Copy To, Duplicate Database and Transfer To", "ios": "no", "linux": "no" }, + { "label": "Users & Roles", "ios": "no", "linux": "no" }, + { "label": "Server Dashboard and table maintenance", "ios": "no", "linux": "no" } ], "limits": [ "Compare & Sync writes a structure script only between two databases of the same engine, with MySQL and MariaDB counted as one. Other pairs compare read-only, MongoDB structures never get a script, and a data sync across engines warns you to check how values are written. To move tables to another engine, use Copy To.", diff --git a/resources/data/content/en/features/sync-and-teams.json b/resources/data/content/en/features/sync-and-teams.json index 155da8db..b853476d 100644 --- a/resources/data/content/en/features/sync-and-teams.json +++ b/resources/data/content/en/features/sync-and-teams.json @@ -108,15 +108,15 @@ } ], "availability": [ - { "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", "iosNote": "Connections, groups and tags, free" }, - { "label": "Password sync through iCloud Keychain", "paid": "icloud-sync", "ios": "yes" }, - { "label": "Choose what syncs and keep connections Local only", "paid": "icloud-sync", "ios": "no" }, - { "label": "Handoff, with iCloud Sync on", "paid": "icloud-sync", "ios": "yes" }, - { "label": "Export and import .tablepro files", "ios": "yes" }, - { "label": "Encrypted Export with passwords", "paid": "encrypted-export", "ios": "yes" }, - { "label": "Linked Folders", "paid": "linked-folders", "ios": "no" }, - { "label": "Team Catalog", "paid": "team-catalog", "ios": "no" }, - { "label": "Team Library", "paid": "team-library", "ios": "no" } + { "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", "iosNote": "Connections, groups and tags, free", "linux": "no" }, + { "label": "Password sync through iCloud Keychain", "paid": "icloud-sync", "ios": "yes", "linux": "no" }, + { "label": "Choose what syncs and keep connections Local only", "paid": "icloud-sync", "ios": "no", "linux": "no" }, + { "label": "Handoff, with iCloud Sync on", "paid": "icloud-sync", "ios": "yes", "linux": "no" }, + { "label": "Export and import .tablepro files", "ios": "yes", "linux": "no" }, + { "label": "Encrypted Export with passwords", "paid": "encrypted-export", "ios": "yes", "linux": "no" }, + { "label": "Linked Folders", "paid": "linked-folders", "ios": "no", "linux": "no" }, + { "label": "Team Catalog", "paid": "team-catalog", "ios": "no", "linux": "no" }, + { "label": "Team Library", "paid": "team-library", "ios": "no", "linux": "no" } ], "limits": [ "When a plan ends on the Mac, iCloud Sync pauses and says why, and connections from Linked Folders and the Team Library disappear from the list. The Team publishing commands are hidden, and Encrypted Export loses its password option.", diff --git a/resources/data/content/es/features/ai-mcp.json b/resources/data/content/es/features/ai-mcp.json index 4026f04a..fafaf61c 100644 --- a/resources/data/content/es/features/ai-mcp.json +++ b/resources/data/content/es/features/ai-mcp.json @@ -130,35 +130,47 @@ "availability": [ { "label": "Chat de IA con tu propio proveedor", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize y Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Sugerencias en línea y AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Sugerencias en línea; sin AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Servidor MCP", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} herramientas, solo por HTTP" }, { "label": "Servidores MCP externos", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript y el comando de shell tablepro", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "El comando tablepro abre archivos" }, { "label": "Extensión Raycast", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/es/features/connections.json b/resources/data/content/es/features/connections.json index 79c878b5..31e0c001 100644 --- a/resources/data/content/es/features/connections.json +++ b/resources/data/content/es/features/connections.json @@ -189,67 +189,87 @@ { "label": "Formulario de conexión, grupos, etiquetas y favoritos", "ios": "partial", - "iosNote": "Una etiqueta por conexión; sin colores" + "iosNote": "Una etiqueta por conexión; sin colores", + "linux": "yes" }, { "label": "Importar desde una URL o el portapapeles", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Importar desde otras apps, carpetas de proyecto y AWS", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo desde otras apps" }, { "label": "Túnel SSH con contraseña o clave privada", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Agente SSH, códigos de un solo uso y hosts de salto", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "TLS con verificación de certificados", "ios": "partial", - "iosNote": "SQL Server cifra sin verificar" + "iosNote": "SQL Server cifra sin verificar", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy y Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM, Kerberos y autenticación de Google", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "En conexiones sincronizadas desde un Mac" + "iosNote": "En conexiones sincronizadas desde un Mac", + "linux": "no" }, { "label": "Contraseñas en el Llavero", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "En el llavero del sistema" }, { "label": "Orígenes de contraseña, perfiles de credenciales y ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Safe Mode por conexión", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Tiempos de espera de conexión y consulta", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo tiempo de espera de consulta" }, { "label": "Nivel mínimo de Safe Mode mediante un perfil de configuración", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/es/features/data-editing.json b/resources/data/content/es/features/data-editing.json index 4def298a..8289497e 100644 --- a/resources/data/content/es/features/data-editing.json +++ b/resources/data/content/es/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "Orden, paginación y filtros", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Filtros guardados y reglas de resaltado", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Navegación por claves foráneas y selector de valores", "ios": "partial", - "iosNote": "Toca para previsualizar la fila referenciada" + "iosNote": "Toca para previsualizar la fila referenciada", + "linux": "partial", + "linuxNote": "Navegación; sin selector de valores" }, { "label": "Editar, insertar y eliminar filas", "ios": "partial", - "iosNote": "Editor de filas; requiere clave primaria" + "iosNote": "Editor de filas; requiere clave primaria", + "linux": "partial", + "linuxNote": "Requiere clave primaria" }, { "label": "Ediciones pendientes, Preview SQL y deshacer", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Ediciones pendientes y deshacer; sin Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Touch ID o contraseña antes de escribir", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Documentos MongoDB y claves Redis", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Las claves Redis solo cambian con comandos" } ], "limits": [ diff --git a/resources/data/content/es/features/import-export.json b/resources/data/content/es/features/import-export.json index 4f0a4d0b..f403670e 100644 --- a/resources/data/content/es/features/import-export.json +++ b/resources/data/content/es/features/import-export.json @@ -136,41 +136,52 @@ { "label": "Importar archivos a tablas", "ios": "partial", - "iosNote": "Acciones de Atajos desde JSON o CSV" + "iosNote": "Acciones de Atajos desde JSON o CSV", + "linux": "partial", + "linuxNote": "CSV a una tabla existente y archivos SQL" }, { "label": "Correspondencias guardadas, Match by Name y Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Exportar tablas y resultados a archivos", "ios": "partial", - "iosNote": "Copiar o compartir JSON, CSV o SQL INSERT" + "iosNote": "Copiar o compartir JSON, CSV o SQL INSERT", + "linux": "yes" }, { "label": "Plugin de exportación {exportPluginFormats}", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Ventana Data Files", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump y Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Dumps SQL sin herramientas externas" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Archivos SQLite y DuckDB", "ios": "partial", - "iosNote": "Archivos del dispositivo o DuckDB en memoria" + "iosNote": "Archivos del dispositivo o DuckDB en memoria", + "linux": "yes" }, { "label": "Archivos SQLite mediante SSH", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/es/features/querying.json b/resources/data/content/es/features/querying.json index 7489e69a..209f6f22 100644 --- a/resources/data/content/es/features/querying.json +++ b/resources/data/content/es/features/querying.json @@ -149,46 +149,58 @@ { "label": "Editor SQL con autocompletado y formato", "ios": "partial", - "iosNote": "Resaltado, Run y Stop; sin autocompletado" + "iosNote": "Resaltado, Run y Stop; sin autocompletado", + "linux": "yes" }, { "label": "Parámetros de consulta", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Historial de consultas", "ios": "partial", - "iosNote": "Las últimas {historyEntriesIos} consultas del dispositivo" + "iosNote": "Las últimas {historyEntriesIos} consultas del dispositivo", + "linux": "yes" }, { "label": "Consultas guardadas y carpetas SQL vinculadas", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo consultas guardadas" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Búsqueda de conexiones y tablas en la barra lateral" }, { "label": "EXPLAIN y EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Vista de mapa", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copiar o compartir resultados", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/es/features/schema.json b/resources/data/content/es/features/schema.json index 12717d41..ac772046 100644 --- a/resources/data/content/es/features/schema.json +++ b/resources/data/content/es/features/schema.json @@ -135,40 +135,50 @@ { "label": "Edición de estructura con vista previa DDL", "ios": "partial", - "iosNote": "Columnas, índices y claves foráneas en solo lectura" + "iosNote": "Columnas, índices y claves foráneas en solo lectura", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Truncar y eliminar tablas", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Diagrama ER", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Rutinas, triggers y carpetas de tablas", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Rutinas y triggers; sin carpetas de tablas" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database y Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard y mantenimiento de tablas", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/es/features/sync-and-teams.json b/resources/data/content/es/features/sync-and-teams.json index 01262ea9..798123dd 100644 --- a/resources/data/content/es/features/sync-and-teams.json +++ b/resources/data/content/es/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "Conexiones, grupos y etiquetas, gratis" + "iosNote": "Conexiones, grupos y etiquetas, gratis", + "linux": "no" }, { "label": "Sincronización de contraseñas mediante el Llavero de iCloud", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Elegir qué sincronizar y mantener conexiones Local only", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Handoff con iCloud Sync activado", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Exportar e importar archivos .tablepro", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Encrypted Export con contraseñas", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/fr/features/ai-mcp.json b/resources/data/content/fr/features/ai-mcp.json index efabf1c1..88efd3c0 100644 --- a/resources/data/content/fr/features/ai-mcp.json +++ b/resources/data/content/fr/features/ai-mcp.json @@ -130,35 +130,47 @@ "availability": [ { "label": "Chat IA avec votre fournisseur", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize et Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Suggestions en ligne et AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Suggestions en ligne ; pas d’AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Serveur MCP", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} outils, en HTTP uniquement" }, { "label": "Serveurs MCP externes", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript et commande shell tablepro", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "La commande tablepro ouvre des fichiers" }, { "label": "Extension Raycast", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/fr/features/connections.json b/resources/data/content/fr/features/connections.json index a284c21c..1b9add00 100644 --- a/resources/data/content/fr/features/connections.json +++ b/resources/data/content/fr/features/connections.json @@ -189,67 +189,87 @@ { "label": "Formulaire de connexion, groupes, tags et favoris", "ios": "partial", - "iosNote": "Un tag par connexion ; pas de couleurs" + "iosNote": "Un tag par connexion ; pas de couleurs", + "linux": "yes" }, { "label": "Import depuis une URL ou le presse-papiers", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Import depuis d’autres apps, des dossiers de projet et AWS", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Depuis d’autres apps uniquement" }, { "label": "Tunnel SSH avec mot de passe ou clé privée", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Agent SSH, codes à usage unique et hôtes de rebond", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "TLS avec vérification des certificats", "ios": "partial", - "iosNote": "SQL Server chiffre sans vérification" + "iosNote": "SQL Server chiffre sans vérification", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy et Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM, Kerberos et authentification Google", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Kerberos uniquement" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Sur les connexions synchronisées depuis un Mac" + "iosNote": "Sur les connexions synchronisées depuis un Mac", + "linux": "no" }, { "label": "Mots de passe dans le Trousseau", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "Dans le trousseau du système" }, { "label": "Sources de mot de passe, profils d’identifiants et ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Safe Mode par connexion", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Délais de connexion et de requête", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Délai de requête uniquement" }, { "label": "Niveau minimal de Safe Mode via un profil de configuration", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/fr/features/data-editing.json b/resources/data/content/fr/features/data-editing.json index af335924..d0182c87 100644 --- a/resources/data/content/fr/features/data-editing.json +++ b/resources/data/content/fr/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "Tri, pagination et filtres", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Filtres enregistrés et règles de surbrillance", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Navigation par clés étrangères et sélecteur de valeurs", "ios": "partial", - "iosNote": "Touchez pour prévisualiser la ligne référencée" + "iosNote": "Touchez pour prévisualiser la ligne référencée", + "linux": "partial", + "linuxNote": "Navigation ; pas de sélecteur de valeurs" }, { "label": "Modifier, insérer et supprimer des lignes", "ios": "partial", - "iosNote": "Éditeur de lignes ; nécessite une clé primaire" + "iosNote": "Éditeur de lignes ; nécessite une clé primaire", + "linux": "partial", + "linuxNote": "Nécessite une clé primaire" }, { "label": "Modifications en attente, Preview SQL et annulation", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Modifications en attente et annulation ; pas de Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Touch ID ou mot de passe avant une écriture", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Documents MongoDB et clés Redis", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Clés Redis modifiables par commandes uniquement" } ], "limits": [ diff --git a/resources/data/content/fr/features/import-export.json b/resources/data/content/fr/features/import-export.json index b840cebe..d64fc7a0 100644 --- a/resources/data/content/fr/features/import-export.json +++ b/resources/data/content/fr/features/import-export.json @@ -136,41 +136,52 @@ { "label": "Importer des fichiers dans une table", "ios": "partial", - "iosNote": "Actions Raccourcis depuis JSON ou CSV" + "iosNote": "Actions Raccourcis depuis JSON ou CSV", + "linux": "partial", + "linuxNote": "CSV vers une table existante, et fichiers SQL" }, { "label": "Correspondances enregistrées, Match by Name et Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Exporter tables et résultats vers des fichiers", "ios": "partial", - "iosNote": "Copier ou partager en JSON, CSV ou SQL INSERT" + "iosNote": "Copier ou partager en JSON, CSV ou SQL INSERT", + "linux": "yes" }, { "label": "Plugin d’export {exportPluginFormats}", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Fenêtre Data Files", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump et Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Dumps SQL sans outils externes" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Fichiers SQLite et DuckDB", "ios": "partial", - "iosNote": "Fichiers sur l’appareil ou DuckDB en mémoire" + "iosNote": "Fichiers sur l’appareil ou DuckDB en mémoire", + "linux": "yes" }, { "label": "Fichiers SQLite via SSH", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/fr/features/querying.json b/resources/data/content/fr/features/querying.json index f5b2de37..7d2740d5 100644 --- a/resources/data/content/fr/features/querying.json +++ b/resources/data/content/fr/features/querying.json @@ -149,46 +149,58 @@ { "label": "Éditeur SQL avec autocomplétion et formatage", "ios": "partial", - "iosNote": "Coloration, Run et Stop ; sans autocomplétion" + "iosNote": "Coloration, Run et Stop ; sans autocomplétion", + "linux": "yes" }, { "label": "Paramètres de requête", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Historique des requêtes", "ios": "partial", - "iosNote": "Les {historyEntriesIos} dernières requêtes de l’appareil" + "iosNote": "Les {historyEntriesIos} dernières requêtes de l’appareil", + "linux": "yes" }, { "label": "Requêtes enregistrées et dossiers SQL liés", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Requêtes enregistrées uniquement" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Recherche des connexions et tables dans la barre latérale" }, { "label": "EXPLAIN et EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Vue Carte", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copier ou partager les résultats", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/fr/features/schema.json b/resources/data/content/fr/features/schema.json index f7d2622d..d3b51b4a 100644 --- a/resources/data/content/fr/features/schema.json +++ b/resources/data/content/fr/features/schema.json @@ -135,40 +135,50 @@ { "label": "Modification de structure avec aperçu DDL", "ios": "partial", - "iosNote": "Colonnes, index et clés étrangères en lecture seule" + "iosNote": "Colonnes, index et clés étrangères en lecture seule", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Tronquer et supprimer des tables", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Diagramme ER", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Routines, déclencheurs et dossiers de tables", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Routines et déclencheurs ; pas de dossiers de tables" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database et Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard et maintenance de tables", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/fr/features/sync-and-teams.json b/resources/data/content/fr/features/sync-and-teams.json index 9ad525c7..099b92b8 100644 --- a/resources/data/content/fr/features/sync-and-teams.json +++ b/resources/data/content/fr/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "Connexions, groupes et tags, gratuit" + "iosNote": "Connexions, groupes et tags, gratuit", + "linux": "no" }, { "label": "Synchronisation des mots de passe via le Trousseau iCloud", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Choisir ce qui se synchronise et garder les connexions Local only", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Handoff avec iCloud Sync activé", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Exporter et importer des fichiers .tablepro", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Encrypted Export avec mots de passe", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/id/features/ai-mcp.json b/resources/data/content/id/features/ai-mcp.json index 7b070a70..a53ef161 100644 --- a/resources/data/content/id/features/ai-mcp.json +++ b/resources/data/content/id/features/ai-mcp.json @@ -130,35 +130,47 @@ "availability": [ { "label": "Chat AI dengan penyedia sendiri", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize, dan Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Saran inline dan AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Saran inline; tanpa AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server MCP", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} tool, hanya lewat HTTP" }, { "label": "Server MCP eksternal", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript dan perintah shell tablepro", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Perintah tablepro membuka file" }, { "label": "Ekstensi Raycast", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/id/features/connections.json b/resources/data/content/id/features/connections.json index d2e22652..bd35357f 100644 --- a/resources/data/content/id/features/connections.json +++ b/resources/data/content/id/features/connections.json @@ -189,67 +189,87 @@ { "label": "Formulir koneksi, grup, tag, dan favorit", "ios": "partial", - "iosNote": "Satu tag per koneksi; tanpa warna" + "iosNote": "Satu tag per koneksi; tanpa warna", + "linux": "yes" }, { "label": "Impor dari URL atau papan klip", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Impor dari aplikasi lain, folder proyek, dan AWS", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Hanya dari aplikasi lain" }, { "label": "Tunnel SSH dengan kata sandi atau private key", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Agen SSH, kode sekali pakai, dan jump host", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "TLS dengan pemeriksaan sertifikat", "ios": "partial", - "iosNote": "SQL Server mengenkripsi tanpa verifikasi" + "iosNote": "SQL Server mengenkripsi tanpa verifikasi", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy, dan Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM, Kerberos, dan login Google", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Hanya Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Pada koneksi yang disinkronkan dari Mac" + "iosNote": "Pada koneksi yang disinkronkan dari Mac", + "linux": "no" }, { "label": "Kata sandi dalam Rantai Kunci", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "Di keyring sistem" }, { "label": "Sumber kata sandi, profil kredensial, dan ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Safe Mode per koneksi", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Batas waktu koneksi dan kueri", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Hanya batas waktu kueri" }, { "label": "Level minimum Safe Mode melalui profil konfigurasi", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/id/features/data-editing.json b/resources/data/content/id/features/data-editing.json index 07fd85d0..97228fd1 100644 --- a/resources/data/content/id/features/data-editing.json +++ b/resources/data/content/id/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "Pengurutan, paginasi, dan filter", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Filter tersimpan dan aturan sorotan", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Navigasi foreign key dan pemilih nilai", "ios": "partial", - "iosNote": "Ketuk untuk melihat pratinjau baris rujukan" + "iosNote": "Ketuk untuk melihat pratinjau baris rujukan", + "linux": "partial", + "linuxNote": "Navigasi; tanpa pemilih nilai" }, { "label": "Edit, sisipkan, dan hapus baris", "ios": "partial", - "iosNote": "Editor baris; memerlukan primary key" + "iosNote": "Editor baris; memerlukan primary key", + "linux": "partial", + "linuxNote": "Memerlukan primary key" }, { "label": "Perubahan tertunda, Preview SQL, dan pembatalan", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Perubahan tertunda dan pembatalan; tanpa Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Touch ID atau kata sandi sebelum penulisan", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Dokumen MongoDB dan kunci Redis", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Kunci Redis hanya diubah lewat perintah" } ], "limits": [ diff --git a/resources/data/content/id/features/import-export.json b/resources/data/content/id/features/import-export.json index 178db50d..58662b01 100644 --- a/resources/data/content/id/features/import-export.json +++ b/resources/data/content/id/features/import-export.json @@ -136,41 +136,52 @@ { "label": "Impor file ke tabel", "ios": "partial", - "iosNote": "Tindakan Pintasan, dari JSON atau CSV" + "iosNote": "Tindakan Pintasan, dari JSON atau CSV", + "linux": "partial", + "linuxNote": "CSV ke tabel yang ada, dan file SQL" }, { "label": "Pemetaan tersimpan, Match by Name, dan Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Ekspor tabel dan hasil ke file", "ios": "partial", - "iosNote": "Salin atau bagikan sebagai JSON, CSV, atau SQL INSERT" + "iosNote": "Salin atau bagikan sebagai JSON, CSV, atau SQL INSERT", + "linux": "yes" }, { "label": "Plugin ekspor {exportPluginFormats}", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Jendela Data Files", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump dan Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Dump SQL tanpa alat eksternal" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "File SQLite dan DuckDB", "ios": "partial", - "iosNote": "File di perangkat atau DuckDB dalam memori" + "iosNote": "File di perangkat atau DuckDB dalam memori", + "linux": "yes" }, { "label": "File SQLite lewat SSH", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/id/features/querying.json b/resources/data/content/id/features/querying.json index 80b6ac28..0327badc 100644 --- a/resources/data/content/id/features/querying.json +++ b/resources/data/content/id/features/querying.json @@ -149,46 +149,58 @@ { "label": "Editor SQL dengan pelengkapan otomatis dan pemformatan", "ios": "partial", - "iosNote": "Sorotan, Run dan Stop; tanpa pelengkapan otomatis" + "iosNote": "Sorotan, Run dan Stop; tanpa pelengkapan otomatis", + "linux": "yes" }, { "label": "Parameter kueri", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Riwayat kueri", "ios": "partial", - "iosNote": "{historyEntriesIos} kueri terakhir di perangkat" + "iosNote": "{historyEntriesIos} kueri terakhir di perangkat", + "linux": "yes" }, { "label": "Kueri tersimpan dan folder SQL tertaut", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Hanya kueri tersimpan" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Cari koneksi dan tabel di sidebar" }, { "label": "EXPLAIN dan EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Tampilan Peta", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Salin atau bagikan hasil", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/id/features/schema.json b/resources/data/content/id/features/schema.json index 45f438ba..3f4e3456 100644 --- a/resources/data/content/id/features/schema.json +++ b/resources/data/content/id/features/schema.json @@ -135,40 +135,50 @@ { "label": "Pengeditan struktur dengan pratinjau DDL", "ios": "partial", - "iosNote": "Kolom, indeks, dan foreign key, hanya-baca" + "iosNote": "Kolom, indeks, dan foreign key, hanya-baca", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Truncate dan hapus tabel", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Diagram ER", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Routine, trigger, dan folder tabel", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Routine dan trigger; tanpa folder tabel" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database, dan Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard dan pemeliharaan tabel", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/id/features/sync-and-teams.json b/resources/data/content/id/features/sync-and-teams.json index e3d18afc..c080f568 100644 --- a/resources/data/content/id/features/sync-and-teams.json +++ b/resources/data/content/id/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "Koneksi, grup, dan tag, gratis" + "iosNote": "Koneksi, grup, dan tag, gratis", + "linux": "no" }, { "label": "Sinkronisasi kata sandi melalui Rantai Kunci iCloud", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Pilih yang disinkronkan dan simpan koneksi Local only", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Handoff dengan iCloud Sync aktif", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Ekspor dan impor file .tablepro", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Encrypted Export dengan kata sandi", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/it/features/ai-mcp.json b/resources/data/content/it/features/ai-mcp.json index 75a31b67..bf6f44e4 100644 --- a/resources/data/content/it/features/ai-mcp.json +++ b/resources/data/content/it/features/ai-mcp.json @@ -130,35 +130,47 @@ "availability": [ { "label": "Chat IA con il tuo provider", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize e Fix with IA", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Suggerimenti in linea e IA Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Suggerimenti in linea; niente AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server MCP", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} strumenti, solo via HTTP" }, { "label": "Server MCP esterni", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript e il comando shell tablepro", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Il comando tablepro apre i file" }, { "label": "Estensione Raycast", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/it/features/connections.json b/resources/data/content/it/features/connections.json index 3fa9bcc8..e571d81f 100644 --- a/resources/data/content/it/features/connections.json +++ b/resources/data/content/it/features/connections.json @@ -189,67 +189,87 @@ { "label": "Modulo di connessione, gruppi, tag e preferiti", "ios": "partial", - "iosNote": "Un tag per connessione; nessun colore" + "iosNote": "Un tag per connessione; nessun colore", + "linux": "yes" }, { "label": "Importazione da URL o appunti", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Importazione da altre app, cartelle di progetto e AWS", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo da altre app" }, { "label": "Tunnel SSH con password o chiave privata", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Agente SSH, codici monouso e jump host", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "TLS con verifica dei certificati", "ios": "partial", - "iosNote": "SQL Server cifra senza verificare" + "iosNote": "SQL Server cifra senza verificare", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy e Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM, Kerberos e accesso Google", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Su connessioni sincronizzate da un Mac" + "iosNote": "Su connessioni sincronizzate da un Mac", + "linux": "no" }, { "label": "Password nel Portachiavi", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "Nel portachiavi di sistema" }, { "label": "Sorgenti delle password, profili di credenziali e ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Safe Mode per connessione", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Timeout di connessione e query", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo timeout delle query" }, { "label": "Livello Safe Mode minimo tramite profilo di configurazione", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/it/features/data-editing.json b/resources/data/content/it/features/data-editing.json index 65ac51d7..90054652 100644 --- a/resources/data/content/it/features/data-editing.json +++ b/resources/data/content/it/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "Ordinamento, paginazione e filtri", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Filtri salvati e regole di evidenziazione", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Navigazione delle chiavi esterne e selettore dei valori", "ios": "partial", - "iosNote": "Tocca per vedere l’anteprima della riga referenziata" + "iosNote": "Tocca per vedere l’anteprima della riga referenziata", + "linux": "partial", + "linuxNote": "Navigazione; nessun selettore dei valori" }, { "label": "Modifica, inserimento ed eliminazione delle righe", "ios": "partial", - "iosNote": "Editor di righe; richiede una chiave primaria" + "iosNote": "Editor di righe; richiede una chiave primaria", + "linux": "partial", + "linuxNote": "Richiede una chiave primaria" }, { "label": "Modifiche in attesa, Preview SQL e annullamento", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Modifiche in attesa e annullamento; niente Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Touch ID o password prima di una scrittura", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Documenti MongoDB e chiavi Redis", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Chiavi Redis modificabili solo con comandi" } ], "limits": [ diff --git a/resources/data/content/it/features/import-export.json b/resources/data/content/it/features/import-export.json index 9057f881..7a139b5a 100644 --- a/resources/data/content/it/features/import-export.json +++ b/resources/data/content/it/features/import-export.json @@ -136,41 +136,52 @@ { "label": "Importazione di file in una tabella", "ios": "partial", - "iosNote": "Azioni di Comandi Rapidi, da JSON o CSV" + "iosNote": "Azioni di Comandi Rapidi, da JSON o CSV", + "linux": "partial", + "linuxNote": "CSV in una tabella esistente e file SQL" }, { "label": "Mappature salvate, Match by Name e Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Esportazione di tabelle e risultati su file", "ios": "partial", - "iosNote": "Copia o condivisione come JSON, CSV o SQL INSERT" + "iosNote": "Copia o condivisione come JSON, CSV o SQL INSERT", + "linux": "yes" }, { "label": "Plugin di esportazione {exportPluginFormats}", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Finestra Data Files", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump e Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Dump SQL senza strumenti esterni" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "File SQLite e DuckDB", "ios": "partial", - "iosNote": "File sul dispositivo oppure DuckDB in memoria" + "iosNote": "File sul dispositivo oppure DuckDB in memoria", + "linux": "yes" }, { "label": "File SQLite tramite SSH", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/it/features/querying.json b/resources/data/content/it/features/querying.json index 1e97d8ed..eac21528 100644 --- a/resources/data/content/it/features/querying.json +++ b/resources/data/content/it/features/querying.json @@ -149,46 +149,58 @@ { "label": "Editor SQL con completamento automatico e formattazione", "ios": "partial", - "iosNote": "Evidenziazione, Run e Stop; nessun completamento automatico" + "iosNote": "Evidenziazione, Run e Stop; nessun completamento automatico", + "linux": "yes" }, { "label": "Parametri di query", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Cronologia delle query", "ios": "partial", - "iosNote": "Le ultime {historyEntriesIos} query sul dispositivo" + "iosNote": "Le ultime {historyEntriesIos} query sul dispositivo", + "linux": "yes" }, { "label": "Query salvate e cartelle SQL collegate", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Solo query salvate" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Ricerca di connessioni e tabelle nella barra laterale" }, { "label": "EXPLAIN ed EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Vista Mappa", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copia o condivisione dei risultati", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/it/features/schema.json b/resources/data/content/it/features/schema.json index 3d39135e..f58a2320 100644 --- a/resources/data/content/it/features/schema.json +++ b/resources/data/content/it/features/schema.json @@ -135,40 +135,50 @@ { "label": "Modifica della struttura con anteprima DDL", "ios": "partial", - "iosNote": "Colonne, indici e chiavi esterne, in sola lettura" + "iosNote": "Colonne, indici e chiavi esterne, in sola lettura", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Troncamento ed eliminazione delle tabelle", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Diagramma ER", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Routine, trigger e cartelle delle tabelle", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Routine e trigger; nessuna cartella delle tabelle" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database e Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard e manutenzione delle tabelle", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/it/features/sync-and-teams.json b/resources/data/content/it/features/sync-and-teams.json index ac863792..20160e00 100644 --- a/resources/data/content/it/features/sync-and-teams.json +++ b/resources/data/content/it/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "Connessioni, gruppi e tag, gratuitamente" + "iosNote": "Connessioni, gruppi e tag, gratuitamente", + "linux": "no" }, { "label": "Sincronizzazione delle password tramite Portachiavi iCloud", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Scelta di cosa sincronizzare e connessioni Local only", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Handoff con iCloud Sync attivo", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Esportazione e importazione di file .tablepro", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Encrypted Export con password", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ja/features/ai-mcp.json b/resources/data/content/ja/features/ai-mcp.json index 2a61c2fe..66f30806 100644 --- a/resources/data/content/ja/features/ai-mcp.json +++ b/resources/data/content/ja/features/ai-mcp.json @@ -125,35 +125,47 @@ "availability": [ { "label": "自分のプロバイダーを使う AI チャット", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review、Explain、Optimize と Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "インライン候補と AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "インライン候補のみ、AI Rules なし" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MCP サーバー", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} 個のツール、HTTP のみ" }, { "label": "外部 MCP サーバー", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript と tablepro シェルコマンド", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "tablepro コマンドでファイルを開く" }, { "label": "Raycast 拡張", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ja/features/connections.json b/resources/data/content/ja/features/connections.json index 24e1688c..d7b3b946 100644 --- a/resources/data/content/ja/features/connections.json +++ b/resources/data/content/ja/features/connections.json @@ -189,67 +189,87 @@ { "label": "接続フォーム、グループ、タグとお気に入り", "ios": "partial", - "iosNote": "接続ごとにタグ一つ、色は非対応" + "iosNote": "接続ごとにタグ一つ、色は非対応", + "linux": "yes" }, { "label": "URL やクリップボードからインポート", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "他アプリ、プロジェクトフォルダーや AWS からインポート", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "他アプリからのみ" }, { "label": "パスワードや秘密鍵による SSH トンネル", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "SSH エージェント、ワンタイムコードと踏み台ホスト", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "証明書検証付き TLS", "ios": "partial", - "iosNote": "SQL Server は検証せずに暗号化" + "iosNote": "SQL Server は検証せずに暗号化", + "linux": "yes" }, { "label": "SOCKS5、Cloudflare Access、Cloud SQL Auth Proxy と Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM、Kerberos と Google 認証", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Kerberos のみ" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Mac から同期した接続のみ" + "iosNote": "Mac から同期した接続のみ", + "linux": "no" }, { "label": "キーチェーンにパスワード保存", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "システムのキーリングに保存" }, { "label": "パスワード取得元、認証情報プロファイルと ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "接続ごとの Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "接続とクエリのタイムアウト", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "クエリのタイムアウトのみ" }, { "label": "構成プロファイルで Safe Mode の最低レベルを強制", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ja/features/data-editing.json b/resources/data/content/ja/features/data-editing.json index e24fe3b3..8180d461 100644 --- a/resources/data/content/ja/features/data-editing.json +++ b/resources/data/content/ja/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "並べ替え、ページ分割とフィルター", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "保存済みフィルターとハイライトルール", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "外部キーの移動と値の選択", "ios": "partial", - "iosNote": "タップして参照先の行をプレビュー" + "iosNote": "タップして参照先の行をプレビュー", + "linux": "partial", + "linuxNote": "移動のみ、値の選択なし" }, { "label": "行の編集、挿入と削除", "ios": "partial", - "iosNote": "行エディター、主キーが必要" + "iosNote": "行エディター、主キーが必要", + "linux": "partial", + "linuxNote": "主キーが必要" }, { "label": "編集の保留、Preview SQL と取り消し", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "編集の保留と取り消し、Preview SQL なし" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "書き込み前に Touch ID やパスワードで確認", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MongoDB ドキュメントと Redis キー", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Redis キーの変更はコマンドのみ" } ], "limits": [ diff --git a/resources/data/content/ja/features/import-export.json b/resources/data/content/ja/features/import-export.json index f4ab5614..d9991ac5 100644 --- a/resources/data/content/ja/features/import-export.json +++ b/resources/data/content/ja/features/import-export.json @@ -136,41 +136,52 @@ { "label": "ファイルをテーブルにインポート", "ios": "partial", - "iosNote": "JSON や CSV のショートカットアクション" + "iosNote": "JSON や CSV のショートカットアクション", + "linux": "partial", + "linuxNote": "既存テーブルへの CSV と SQL ファイル" }, { "label": "対応付けの保存、Match by Name と Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "テーブルと結果をファイルにエクスポート", "ios": "partial", - "iosNote": "JSON、CSV や SQL INSERT としてコピー・共有" + "iosNote": "JSON、CSV や SQL INSERT としてコピー・共有", + "linux": "yes" }, { "label": "{exportPluginFormats} エクスポートプラグイン", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Files ウィンドウ", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump と Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "外部ツール不要の SQL ダンプ" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "SQLite と DuckDB ファイル", "ios": "partial", - "iosNote": "デバイス上のファイルやインメモリ DuckDB" + "iosNote": "デバイス上のファイルやインメモリ DuckDB", + "linux": "yes" }, { "label": "SSH 経由の SQLite ファイル", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ja/features/querying.json b/resources/data/content/ja/features/querying.json index b3c51b3c..662f6ad4 100644 --- a/resources/data/content/ja/features/querying.json +++ b/resources/data/content/ja/features/querying.json @@ -149,46 +149,58 @@ { "label": "自動補完と整形付き SQL エディター", "ios": "partial", - "iosNote": "ハイライト、Run と Stop のみ。自動補完なし" + "iosNote": "ハイライト、Run と Stop のみ。自動補完なし", + "linux": "yes" }, { "label": "クエリパラメーター", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "クエリ履歴", "ios": "partial", - "iosNote": "デバイス上の直近 {historyEntriesIos} 件" + "iosNote": "デバイス上の直近 {historyEntriesIos} 件", + "linux": "yes" }, { "label": "保存済みクエリとリンクした SQL フォルダー", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "保存済みクエリのみ" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "サイドバーで接続とテーブルを検索" }, { "label": "EXPLAIN と EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "地図ビュー", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "結果をコピーまたは共有", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/ja/features/schema.json b/resources/data/content/ja/features/schema.json index d6e8fdc3..6aac10f9 100644 --- a/resources/data/content/ja/features/schema.json +++ b/resources/data/content/ja/features/schema.json @@ -135,40 +135,50 @@ { "label": "DDL プレビュー付き構造編集", "ios": "partial", - "iosNote": "カラム、インデックスと外部キーは読み取り専用" + "iosNote": "カラム、インデックスと外部キーは読み取り専用", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "テーブルを空にする・削除する", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "ER 図", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "ルーチン、トリガーとテーブルフォルダー", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "ルーチンとトリガーのみ、テーブルフォルダーなし" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To、Duplicate Database と Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard とテーブル保守", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ja/features/sync-and-teams.json b/resources/data/content/ja/features/sync-and-teams.json index 280bf94f..4f08dc5b 100644 --- a/resources/data/content/ja/features/sync-and-teams.json +++ b/resources/data/content/ja/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "接続、グループとタグ、無料" + "iosNote": "接続、グループとタグ、無料", + "linux": "no" }, { "label": "iCloud キーチェーン経由のパスワード同期", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "同期対象の選択と Local only 接続", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "iCloud Sync 有効時の Handoff", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": ".tablepro ファイルのエクスポートとインポート", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "パスワード付き Encrypted Export", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ko/features/ai-mcp.json b/resources/data/content/ko/features/ai-mcp.json index 4bd44b23..3e402d5f 100644 --- a/resources/data/content/ko/features/ai-mcp.json +++ b/resources/data/content/ko/features/ai-mcp.json @@ -125,35 +125,47 @@ "availability": [ { "label": "직접 선택한 제공업체를 사용하는 AI 채팅", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize 및 Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "인라인 제안 및 AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "인라인 제안; AI Rules 없음" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MCP 서버", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux}개 도구, HTTP 전용" }, { "label": "외부 MCP 서버", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript 및 tablepro 셸 명령", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "tablepro 명령으로 파일 열기" }, { "label": "Raycast 확장", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ko/features/connections.json b/resources/data/content/ko/features/connections.json index 58a94df4..808289aa 100644 --- a/resources/data/content/ko/features/connections.json +++ b/resources/data/content/ko/features/connections.json @@ -189,67 +189,87 @@ { "label": "연결 양식, 그룹, 태그 및 즐겨찾기", "ios": "partial", - "iosNote": "연결당 태그 하나, 색상 없음" + "iosNote": "연결당 태그 하나, 색상 없음", + "linux": "yes" }, { "label": "URL 또는 클립보드에서 가져오기", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "다른 앱, 프로젝트 폴더 및 AWS에서 가져오기", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "다른 앱에서만" }, { "label": "비밀번호 또는 개인 키를 사용하는 SSH 터널", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "SSH 에이전트, 일회용 코드 및 점프 호스트", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "인증서 검사를 사용하는 TLS", "ios": "partial", - "iosNote": "SQL Server는 검증 없이 암호화" + "iosNote": "SQL Server는 검증 없이 암호화", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy 및 Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM, Kerberos 및 Google 로그인", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Kerberos만" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Mac에서 동기화한 연결에서" + "iosNote": "Mac에서 동기화한 연결에서", + "linux": "no" }, { "label": "키체인에 비밀번호 저장", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "시스템 키링에 저장" }, { "label": "비밀번호 소스, 자격 증명 프로필 및 ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "연결별 Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "연결 및 쿼리 제한 시간", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "쿼리 제한 시간만" }, { "label": "구성 프로필을 통한 최소 Safe Mode 수준", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ko/features/data-editing.json b/resources/data/content/ko/features/data-editing.json index 014e81e3..8cda7f2d 100644 --- a/resources/data/content/ko/features/data-editing.json +++ b/resources/data/content/ko/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "정렬, 페이지 처리 및 필터", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "저장된 필터 및 강조 규칙", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "외래 키 탐색 및 값 선택기", "ios": "partial", - "iosNote": "탭하여 참조 행 미리 보기" + "iosNote": "탭하여 참조 행 미리 보기", + "linux": "partial", + "linuxNote": "탐색만; 값 선택기 없음" }, { "label": "행 편집, 삽입 및 삭제", "ios": "partial", - "iosNote": "행 편집기; 기본 키 필요" + "iosNote": "행 편집기; 기본 키 필요", + "linux": "partial", + "linuxNote": "기본 키 필요" }, { "label": "편집 보류, Preview SQL 및 실행 취소", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "편집 보류 및 실행 취소; Preview SQL 없음" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "쓰기 전 Touch ID 또는 비밀번호 확인", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MongoDB 문서 및 Redis 키", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Redis 키는 명령으로만 변경" } ], "limits": [ diff --git a/resources/data/content/ko/features/import-export.json b/resources/data/content/ko/features/import-export.json index 18b28169..e9cb4431 100644 --- a/resources/data/content/ko/features/import-export.json +++ b/resources/data/content/ko/features/import-export.json @@ -136,41 +136,52 @@ { "label": "파일을 테이블로 가져오기", "ios": "partial", - "iosNote": "JSON 또는 CSV를 사용하는 단축어 동작" + "iosNote": "JSON 또는 CSV를 사용하는 단축어 동작", + "linux": "partial", + "linuxNote": "기존 테이블로 CSV 가져오기 및 SQL 파일" }, { "label": "저장된 매핑, Match by Name 및 Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "테이블과 결과를 파일로 내보내기", "ios": "partial", - "iosNote": "JSON, CSV 또는 SQL INSERT로 복사·공유" + "iosNote": "JSON, CSV 또는 SQL INSERT로 복사·공유", + "linux": "yes" }, { "label": "{exportPluginFormats} 내보내기 플러그인", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Files 창", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump 및 Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "외부 도구 없이 만드는 SQL 덤프" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "SQLite 및 DuckDB 파일", "ios": "partial", - "iosNote": "기기의 파일 또는 메모리 내 DuckDB" + "iosNote": "기기의 파일 또는 메모리 내 DuckDB", + "linux": "yes" }, { "label": "SSH를 통한 SQLite 파일", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ko/features/querying.json b/resources/data/content/ko/features/querying.json index 1d7647f7..7d68099d 100644 --- a/resources/data/content/ko/features/querying.json +++ b/resources/data/content/ko/features/querying.json @@ -149,46 +149,58 @@ { "label": "자동 완성 및 서식 정리를 지원하는 SQL 편집기", "ios": "partial", - "iosNote": "구문 강조, Run과 Stop 지원; 자동 완성 없음" + "iosNote": "구문 강조, Run과 Stop 지원; 자동 완성 없음", + "linux": "yes" }, { "label": "쿼리 매개변수", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "쿼리 기록", "ios": "partial", - "iosNote": "기기의 최근 {historyEntriesIos}개 쿼리" + "iosNote": "기기의 최근 {historyEntriesIos}개 쿼리", + "linux": "yes" }, { "label": "저장된 쿼리 및 연결된 SQL 폴더", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "저장된 쿼리만" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "사이드바에서 연결과 테이블 검색" }, { "label": "EXPLAIN 및 EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "지도 보기", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "결과 복사 또는 공유", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/ko/features/schema.json b/resources/data/content/ko/features/schema.json index 67b4c079..94cb5982 100644 --- a/resources/data/content/ko/features/schema.json +++ b/resources/data/content/ko/features/schema.json @@ -135,40 +135,50 @@ { "label": "DDL 미리 보기를 통한 구조 편집", "ios": "partial", - "iosNote": "열, 인덱스 및 외래 키는 읽기 전용" + "iosNote": "열, 인덱스 및 외래 키는 읽기 전용", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "테이블 비우기 및 삭제", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "ER 다이어그램", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "루틴, 트리거 및 테이블 폴더", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "루틴과 트리거; 테이블 폴더 없음" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database 및 Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard 및 테이블 유지 관리", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/ko/features/sync-and-teams.json b/resources/data/content/ko/features/sync-and-teams.json index 378090f1..14b96352 100644 --- a/resources/data/content/ko/features/sync-and-teams.json +++ b/resources/data/content/ko/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "연결, 그룹과 태그; 무료" + "iosNote": "연결, 그룹과 태그; 무료", + "linux": "no" }, { "label": "iCloud 키체인을 통한 비밀번호 동기화", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "동기화 항목 선택 및 Local only 연결", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "iCloud Sync를 켠 상태의 Handoff", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": ".tablepro 파일 내보내기 및 가져오기", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "비밀번호를 포함한 Encrypted Export", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/pt-BR/features/ai-mcp.json b/resources/data/content/pt-BR/features/ai-mcp.json index c1953dc5..e75cafcf 100644 --- a/resources/data/content/pt-BR/features/ai-mcp.json +++ b/resources/data/content/pt-BR/features/ai-mcp.json @@ -130,35 +130,47 @@ "availability": [ { "label": "Chat de IA com seu próprio provedor", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize e Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Sugestões em linha e AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Sugestões em linha; sem AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Servidor MCP", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} ferramentas, somente por HTTP" }, { "label": "Servidores MCP externos", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript e o comando de shell tablepro", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "O comando tablepro abre arquivos" }, { "label": "Extensão Raycast", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/pt-BR/features/connections.json b/resources/data/content/pt-BR/features/connections.json index f7bbcd55..5d854e5d 100644 --- a/resources/data/content/pt-BR/features/connections.json +++ b/resources/data/content/pt-BR/features/connections.json @@ -189,67 +189,87 @@ { "label": "Formulário de conexão, grupos, etiquetas e favoritos", "ios": "partial", - "iosNote": "Uma etiqueta por conexão; sem cores" + "iosNote": "Uma etiqueta por conexão; sem cores", + "linux": "yes" }, { "label": "Importar de URL ou área de transferência", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Importar de outros apps, pastas de projeto e AWS", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Somente de outros apps" }, { "label": "Túnel SSH com senha ou chave privada", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Agente SSH, códigos de uso único e hosts intermediários", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "TLS com verificação de certificado", "ios": "partial", - "iosNote": "SQL Server criptografa sem verificar" + "iosNote": "SQL Server criptografa sem verificar", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy e Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM, Kerberos e autenticação Google", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Somente Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Em conexões sincronizadas de um Mac" + "iosNote": "Em conexões sincronizadas de um Mac", + "linux": "no" }, { "label": "Senhas nas Chaves", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "No chaveiro do sistema" }, { "label": "Fontes de senha, perfis de credenciais e ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Safe Mode por conexão", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Tempos limite de conexão e consulta", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Somente tempo limite de consulta" }, { "label": "Nível mínimo de Safe Mode por perfil de configuração", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/pt-BR/features/data-editing.json b/resources/data/content/pt-BR/features/data-editing.json index 692f7fc9..67158538 100644 --- a/resources/data/content/pt-BR/features/data-editing.json +++ b/resources/data/content/pt-BR/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "Ordenação, paginação e filtros", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Filtros salvos e regras de destaque", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Navegação por chaves estrangeiras e seletor de valores", "ios": "partial", - "iosNote": "Toque para ver a prévia da linha referenciada" + "iosNote": "Toque para ver a prévia da linha referenciada", + "linux": "partial", + "linuxNote": "Navegação; sem seletor de valores" }, { "label": "Editar, inserir e excluir linhas", "ios": "partial", - "iosNote": "Editor de linhas; exige chave primária" + "iosNote": "Editor de linhas; exige chave primária", + "linux": "partial", + "linuxNote": "Exige chave primária" }, { "label": "Edições pendentes, Preview SQL e desfazer", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Edições pendentes e desfazer; sem Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Touch ID ou senha antes de gravar", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Documentos MongoDB e chaves Redis", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Chaves Redis mudam somente por comandos" } ], "limits": [ diff --git a/resources/data/content/pt-BR/features/import-export.json b/resources/data/content/pt-BR/features/import-export.json index 38ea5662..6588e3aa 100644 --- a/resources/data/content/pt-BR/features/import-export.json +++ b/resources/data/content/pt-BR/features/import-export.json @@ -136,41 +136,52 @@ { "label": "Importar arquivos para uma tabela", "ios": "partial", - "iosNote": "Ações dos Atalhos, a partir de JSON ou CSV" + "iosNote": "Ações dos Atalhos, a partir de JSON ou CSV", + "linux": "partial", + "linuxNote": "CSV para uma tabela existente e arquivos SQL" }, { "label": "Mapeamentos salvos, Match by Name e Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Exportar tabelas e resultados para arquivos", "ios": "partial", - "iosNote": "Copiar ou compartilhar como JSON, CSV ou SQL INSERT" + "iosNote": "Copiar ou compartilhar como JSON, CSV ou SQL INSERT", + "linux": "yes" }, { "label": "Plugin de exportação {exportPluginFormats}", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Janela Data Files", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump e Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Dumps SQL sem ferramentas externas" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Arquivos SQLite e DuckDB", "ios": "partial", - "iosNote": "Arquivos no dispositivo ou DuckDB em memória" + "iosNote": "Arquivos no dispositivo ou DuckDB em memória", + "linux": "yes" }, { "label": "Arquivos SQLite por SSH", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/pt-BR/features/querying.json b/resources/data/content/pt-BR/features/querying.json index 66d23ce3..ca26dabf 100644 --- a/resources/data/content/pt-BR/features/querying.json +++ b/resources/data/content/pt-BR/features/querying.json @@ -149,46 +149,58 @@ { "label": "Editor SQL com preenchimento automático e formatação", "ios": "partial", - "iosNote": "Destaque, Run e Stop; sem preenchimento automático" + "iosNote": "Destaque, Run e Stop; sem preenchimento automático", + "linux": "yes" }, { "label": "Parâmetros de consulta", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Histórico de consultas", "ios": "partial", - "iosNote": "As últimas {historyEntriesIos} consultas no dispositivo" + "iosNote": "As últimas {historyEntriesIos} consultas no dispositivo", + "linux": "yes" }, { "label": "Consultas salvas e pastas SQL vinculadas", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Somente consultas salvas" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Busca de conexões e tabelas na barra lateral" }, { "label": "EXPLAIN e EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Visualização Mapa", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copiar ou compartilhar resultados", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/pt-BR/features/schema.json b/resources/data/content/pt-BR/features/schema.json index 3667a1cb..a03c3457 100644 --- a/resources/data/content/pt-BR/features/schema.json +++ b/resources/data/content/pt-BR/features/schema.json @@ -135,40 +135,50 @@ { "label": "Edição de estrutura com prévia DDL", "ios": "partial", - "iosNote": "Colunas, índices e chaves estrangeiras, somente leitura" + "iosNote": "Colunas, índices e chaves estrangeiras, somente leitura", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Truncar e remover tabelas", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Diagrama ER", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Rotinas, triggers e pastas de tabelas", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Rotinas e triggers; sem pastas de tabelas" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database e Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard e manutenção de tabelas", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/pt-BR/features/sync-and-teams.json b/resources/data/content/pt-BR/features/sync-and-teams.json index 4b13d4c0..4d22d462 100644 --- a/resources/data/content/pt-BR/features/sync-and-teams.json +++ b/resources/data/content/pt-BR/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "Conexões, grupos e etiquetas, gratuitamente" + "iosNote": "Conexões, grupos e etiquetas, gratuitamente", + "linux": "no" }, { "label": "Sincronização de senhas pelas Chaves do iCloud", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Escolher o que sincronizar e manter conexões Local only", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Handoff com o iCloud Sync ativado", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Exportar e importar arquivos .tablepro", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Encrypted Export com senhas", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/vi/features/ai-mcp.json b/resources/data/content/vi/features/ai-mcp.json index b45bf3cb..f57da1f9 100644 --- a/resources/data/content/vi/features/ai-mcp.json +++ b/resources/data/content/vi/features/ai-mcp.json @@ -130,35 +130,47 @@ "availability": [ { "label": "AI chat với nhà cung cấp AI của bạn", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review, Explain, Optimize và Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Gợi ý nội tuyến và AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Gợi ý nội tuyến; không có AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MCP server", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} tool, chỉ qua HTTP" }, { "label": "MCP server bên ngoài", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript và lệnh shell tablepro", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Lệnh tablepro mở file" }, { "label": "Extension Raycast", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/vi/features/connections.json b/resources/data/content/vi/features/connections.json index 44d0ec66..212bcae6 100644 --- a/resources/data/content/vi/features/connections.json +++ b/resources/data/content/vi/features/connections.json @@ -189,67 +189,87 @@ { "label": "Form connection, nhóm, tag và mục yêu thích", "ios": "partial", - "iosNote": "Một tag cho mỗi connection; không có màu" + "iosNote": "Một tag cho mỗi connection; không có màu", + "linux": "yes" }, { "label": "Import từ URL hoặc clipboard", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Import từ ứng dụng khác, thư mục dự án và AWS", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Chỉ từ ứng dụng khác" }, { "label": "SSH tunnel bằng mật khẩu hoặc private key", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "SSH agent, mã dùng một lần và jump host", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "TLS có kiểm tra chứng chỉ", "ios": "partial", - "iosNote": "SQL Server mã hóa nhưng không kiểm tra" + "iosNote": "SQL Server mã hóa nhưng không kiểm tra", + "linux": "yes" }, { "label": "SOCKS5, Cloudflare Access, Cloud SQL Auth Proxy và Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Đăng nhập bằng AWS IAM, Kerberos và Google", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Chỉ Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "Với connection đồng bộ từ Mac" + "iosNote": "Với connection đồng bộ từ Mac", + "linux": "no" }, { "label": "Lưu mật khẩu trong Keychain", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "Trong keyring của hệ thống" }, { "label": "Nguồn mật khẩu, credential profile và ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Safe Mode cho từng connection", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Connect timeout và query timeout", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Chỉ có query timeout" }, { "label": "Mức Safe Mode tối thiểu qua configuration profile", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/vi/features/data-editing.json b/resources/data/content/vi/features/data-editing.json index c879344e..4bbb83f2 100644 --- a/resources/data/content/vi/features/data-editing.json +++ b/resources/data/content/vi/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "Sắp xếp, phân trang và bộ lọc", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "Bộ lọc đã lưu và highlight rules", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Đi theo khóa ngoại và bộ chọn giá trị", "ios": "partial", - "iosNote": "Chạm để xem nhanh dòng được tham chiếu" + "iosNote": "Chạm để xem nhanh dòng được tham chiếu", + "linux": "partial", + "linuxNote": "Đi theo khóa ngoại; không có bộ chọn giá trị" }, { "label": "Sửa, thêm và xóa dòng", "ios": "partial", - "iosNote": "Trình sửa từng dòng; cần khóa chính" + "iosNote": "Trình sửa từng dòng; cần khóa chính", + "linux": "partial", + "linuxNote": "Cần khóa chính" }, { "label": "Thay đổi đang chờ lưu, Preview SQL và undo", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Thay đổi đang chờ lưu và undo; không có Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "Touch ID hoặc mật khẩu trước câu lệnh ghi", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Document MongoDB và key Redis", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Key Redis chỉ sửa được bằng lệnh" } ], "limits": [ diff --git a/resources/data/content/vi/features/import-export.json b/resources/data/content/vi/features/import-export.json index 68f2fcc2..dd1f233b 100644 --- a/resources/data/content/vi/features/import-export.json +++ b/resources/data/content/vi/features/import-export.json @@ -136,41 +136,52 @@ { "label": "Import file vào table", "ios": "partial", - "iosNote": "Tác vụ Phím tắt, từ JSON hoặc CSV" + "iosNote": "Tác vụ Phím tắt, từ JSON hoặc CSV", + "linux": "partial", + "linuxNote": "CSV vào table có sẵn, và file SQL" }, { "label": "Ghép cột đã lưu, Match by Name và Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Export table và kết quả query ra file", "ios": "partial", - "iosNote": "Sao chép hoặc chia sẻ dưới dạng JSON, CSV hoặc SQL INSERT" + "iosNote": "Sao chép hoặc chia sẻ dưới dạng JSON, CSV hoặc SQL INSERT", + "linux": "yes" }, { "label": "Plugin export {exportPluginFormats}", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Cửa sổ Data Files", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump và Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Dump SQL không cần công cụ ngoài" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "File SQLite và DuckDB", "ios": "partial", - "iosNote": "File trên thiết bị, hoặc DuckDB trong bộ nhớ" + "iosNote": "File trên thiết bị, hoặc DuckDB trong bộ nhớ", + "linux": "yes" }, { "label": "File SQLite qua SSH", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/vi/features/querying.json b/resources/data/content/vi/features/querying.json index 864742e1..2946b3d3 100644 --- a/resources/data/content/vi/features/querying.json +++ b/resources/data/content/vi/features/querying.json @@ -149,46 +149,58 @@ { "label": "SQL editor có autocomplete và định dạng", "ios": "partial", - "iosNote": "Tô sáng cú pháp, Run và Stop; không có autocomplete" + "iosNote": "Tô sáng cú pháp, Run và Stop; không có autocomplete", + "linux": "yes" }, { "label": "Tham số trong query", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Lịch sử query", "ios": "partial", - "iosNote": "{historyEntriesIos} query gần nhất trên thiết bị" + "iosNote": "{historyEntriesIos} query gần nhất trên thiết bị", + "linux": "yes" }, { "label": "Query đã lưu và linked SQL folder", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Chỉ có query đã lưu" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Tìm connection và table trong sidebar" }, { "label": "EXPLAIN và EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Map view", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Sao chép hoặc chia sẻ kết quả", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/vi/features/schema.json b/resources/data/content/vi/features/schema.json index af5d2203..a2f55633 100644 --- a/resources/data/content/vi/features/schema.json +++ b/resources/data/content/vi/features/schema.json @@ -135,40 +135,50 @@ { "label": "Sửa cấu trúc với bản xem trước DDL", "ios": "partial", - "iosNote": "Cột, index và khóa ngoại, chỉ đọc" + "iosNote": "Cột, index và khóa ngoại, chỉ đọc", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Truncate và drop table", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "ER diagram", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "Routine, trigger và thư mục table", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Routine và trigger; không có thư mục table" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To, Duplicate Database và Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard và bảo trì table", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/vi/features/sync-and-teams.json b/resources/data/content/vi/features/sync-and-teams.json index b3aac37b..4fdb0eae 100644 --- a/resources/data/content/vi/features/sync-and-teams.json +++ b/resources/data/content/vi/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "Connection, nhóm và tag; miễn phí" + "iosNote": "Connection, nhóm và tag; miễn phí", + "linux": "no" }, { "label": "Đồng bộ mật khẩu qua iCloud Keychain", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Chọn mục đồng bộ và giữ connection ở chế độ Chỉ cục bộ", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Handoff, khi bật iCloud Sync", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Export và import file .tablepro", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Encrypted Export kèm mật khẩu", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hans/features/ai-mcp.json b/resources/data/content/zh-Hans/features/ai-mcp.json index 6b684375..290b673a 100644 --- a/resources/data/content/zh-Hans/features/ai-mcp.json +++ b/resources/data/content/zh-Hans/features/ai-mcp.json @@ -125,35 +125,47 @@ "availability": [ { "label": "使用自选提供商的 AI 聊天", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review、Explain、Optimize 和 Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "行内建议和 AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "行内建议;不支持 AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MCP 服务器", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} 个工具,仅限 HTTP" }, { "label": "外部 MCP 服务器", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript 和 tablepro shell 命令", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "tablepro 命令可打开文件" }, { "label": "Raycast 扩展", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hans/features/connections.json b/resources/data/content/zh-Hans/features/connections.json index c2391493..bf1d59e9 100644 --- a/resources/data/content/zh-Hans/features/connections.json +++ b/resources/data/content/zh-Hans/features/connections.json @@ -189,67 +189,87 @@ { "label": "连接表单、分组、标签和收藏", "ios": "partial", - "iosNote": "每个连接一个标签;不支持颜色" + "iosNote": "每个连接一个标签;不支持颜色", + "linux": "yes" }, { "label": "从 URL 或剪贴板导入", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "从其他应用、项目文件夹和 AWS 导入", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "仅从其他应用导入" }, { "label": "使用密码或私钥的 SSH 隧道", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "SSH agent、一次性验证码和跳板机", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "带证书验证的 TLS", "ios": "partial", - "iosNote": "SQL Server 仅加密,不验证" + "iosNote": "SQL Server 仅加密,不验证", + "linux": "yes" }, { "label": "SOCKS5、Cloudflare Access、Cloud SQL Auth Proxy 和 Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM、Kerberos 和 Google 登录", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "仅 Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "用于从 Mac 同步的连接" + "iosNote": "用于从 Mac 同步的连接", + "linux": "no" }, { "label": "密码保存在钥匙串中", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "保存在系统密钥环中" }, { "label": "密码来源、凭据配置文件和 ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "每个连接的 Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "连接和查询超时", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "仅查询超时" }, { "label": "通过配置描述文件设置最低 Safe Mode 级别", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hans/features/data-editing.json b/resources/data/content/zh-Hans/features/data-editing.json index 68aefa40..e3ecb4c7 100644 --- a/resources/data/content/zh-Hans/features/data-editing.json +++ b/resources/data/content/zh-Hans/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "排序、分页和筛选", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "已保存筛选和高亮规则", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "外键导航和值选择器", "ios": "partial", - "iosNote": "点按预览引用行" + "iosNote": "点按预览引用行", + "linux": "partial", + "linuxNote": "支持导航;不支持值选择器" }, { "label": "编辑、插入和删除行", "ios": "partial", - "iosNote": "行编辑器;需要主键" + "iosNote": "行编辑器;需要主键", + "linux": "partial", + "linuxNote": "需要主键" }, { "label": "暂存编辑、Preview SQL 和撤销", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "暂存编辑和撤销;不支持 Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "写入前要求 Touch ID 或密码", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MongoDB 文档和 Redis 键", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Redis 键只能通过命令修改" } ], "limits": [ diff --git a/resources/data/content/zh-Hans/features/import-export.json b/resources/data/content/zh-Hans/features/import-export.json index b6c0013a..d5d881c2 100644 --- a/resources/data/content/zh-Hans/features/import-export.json +++ b/resources/data/content/zh-Hans/features/import-export.json @@ -136,41 +136,52 @@ { "label": "将文件导入表", "ios": "partial", - "iosNote": "通过快捷指令从 JSON 或 CSV 导入" + "iosNote": "通过快捷指令从 JSON 或 CSV 导入", + "linux": "partial", + "linuxNote": "CSV 导入现有表,以及 SQL 文件" }, { "label": "已保存映射、Match by Name 和 Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "将表和结果导出为文件", "ios": "partial", - "iosNote": "复制或分享为 JSON、CSV 或 SQL INSERT" + "iosNote": "复制或分享为 JSON、CSV 或 SQL INSERT", + "linux": "yes" }, { "label": "{exportPluginFormats} 导出插件", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Files 窗口", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump 和 Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "无需外部工具的 SQL 转储" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "SQLite 和 DuckDB 文件", "ios": "partial", - "iosNote": "设备上的文件,或内存中的 DuckDB" + "iosNote": "设备上的文件,或内存中的 DuckDB", + "linux": "yes" }, { "label": "通过 SSH 访问 SQLite 文件", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hans/features/querying.json b/resources/data/content/zh-Hans/features/querying.json index d7da70f8..427a455c 100644 --- a/resources/data/content/zh-Hans/features/querying.json +++ b/resources/data/content/zh-Hans/features/querying.json @@ -149,46 +149,58 @@ { "label": "支持自动补全和格式化的 SQL 编辑器", "ios": "partial", - "iosNote": "高亮、Run 和 Stop;不支持自动补全" + "iosNote": "高亮、Run 和 Stop;不支持自动补全", + "linux": "yes" }, { "label": "查询参数", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "查询历史", "ios": "partial", - "iosNote": "设备上最近 {historyEntriesIos} 条查询" + "iosNote": "设备上最近 {historyEntriesIos} 条查询", + "linux": "yes" }, { "label": "已保存查询和关联 SQL 文件夹", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "仅已保存查询" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "在侧边栏搜索连接和表" }, { "label": "EXPLAIN 和 EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "地图视图", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "复制或分享结果", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/zh-Hans/features/schema.json b/resources/data/content/zh-Hans/features/schema.json index aab92a6c..9a77f1f7 100644 --- a/resources/data/content/zh-Hans/features/schema.json +++ b/resources/data/content/zh-Hans/features/schema.json @@ -135,40 +135,50 @@ { "label": "带 DDL 预览的结构编辑", "ios": "partial", - "iosNote": "列、索引和外键只读" + "iosNote": "列、索引和外键只读", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "清空和删除表", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "ER 图", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "例程、触发器和表文件夹", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "例程和触发器;不支持表文件夹" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To、Duplicate Database 和 Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard 和表维护", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hans/features/sync-and-teams.json b/resources/data/content/zh-Hans/features/sync-and-teams.json index 3e0cb0be..10c6bab5 100644 --- a/resources/data/content/zh-Hans/features/sync-and-teams.json +++ b/resources/data/content/zh-Hans/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "连接、分组和标签,免费" + "iosNote": "连接、分组和标签,免费", + "linux": "no" }, { "label": "通过 iCloud 钥匙串同步密码", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "选择同步内容,将连接设为 Local only", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "启用 iCloud Sync 时使用 Handoff", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "导出和导入 .tablepro 文件", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "含密码的 Encrypted Export", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hant/features/ai-mcp.json b/resources/data/content/zh-Hant/features/ai-mcp.json index ba05d831..84b29f4e 100644 --- a/resources/data/content/zh-Hant/features/ai-mcp.json +++ b/resources/data/content/zh-Hant/features/ai-mcp.json @@ -125,35 +125,47 @@ "availability": [ { "label": "使用自選供應商的 AI 聊天", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{aiProvidersLinux}" }, { "label": "Review、Explain、Optimize 與 Fix with AI", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "行內建議與 AI Rules", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "行內建議;不支援 AI Rules" }, { "label": "Agent mode", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MCP 伺服器", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "{mcpToolCountLinux} 個工具,僅限 HTTP" }, { "label": "外部 MCP 伺服器", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AppleScript 與 tablepro shell 指令", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "tablepro 指令可開啟檔案" }, { "label": "Raycast 擴充功能", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hant/features/connections.json b/resources/data/content/zh-Hant/features/connections.json index 07fc05c8..a740da8b 100644 --- a/resources/data/content/zh-Hant/features/connections.json +++ b/resources/data/content/zh-Hant/features/connections.json @@ -189,67 +189,87 @@ { "label": "連線表單、群組、標籤與喜好項目", "ios": "partial", - "iosNote": "每個連線一個標籤;不支援顏色" + "iosNote": "每個連線一個標籤;不支援顏色", + "linux": "yes" }, { "label": "從 URL 或剪貼簿匯入", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "從其他 App、專案資料夾與 AWS 匯入", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "僅從其他 App 匯入" }, { "label": "使用密碼或私密金鑰的 SSH 通道", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "SSH 代理程式、一次性驗證碼與跳板主機", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "含憑證驗證的 TLS", "ios": "partial", - "iosNote": "SQL Server 僅加密,不驗證" + "iosNote": "SQL Server 僅加密,不驗證", + "linux": "yes" }, { "label": "SOCKS5、Cloudflare Access、Cloud SQL Auth Proxy 與 Tunnel Command", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "AWS IAM、Kerberos 與 Google 登入", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "僅 Kerberos" }, { "label": "Microsoft Entra ID", "ios": "partial", - "iosNote": "用於從 Mac 同步的連線" + "iosNote": "用於從 Mac 同步的連線", + "linux": "no" }, { "label": "密碼儲存在鑰匙圈中", - "ios": "yes" + "ios": "yes", + "linux": "partial", + "linuxNote": "儲存在系統金鑰圈中" }, { "label": "密碼來源、認證資訊設定檔與 ~/.pgpass", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Environment Variables", "paid": "environment-variables", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "每個連線的 Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "連線與查詢逾時", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "僅查詢逾時" }, { "label": "透過設定描述檔設定最低 Safe Mode 等級", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hant/features/data-editing.json b/resources/data/content/zh-Hant/features/data-editing.json index 02d30722..976a4190 100644 --- a/resources/data/content/zh-Hant/features/data-editing.json +++ b/resources/data/content/zh-Hant/features/data-editing.json @@ -122,43 +122,57 @@ "availability": [ { "label": "排序、分頁與篩選", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "已儲存篩選與醒目提示規則", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "外部索引鍵導覽與值選擇器", "ios": "partial", - "iosNote": "點一下預覽參照列" + "iosNote": "點一下預覽參照列", + "linux": "partial", + "linuxNote": "支援導覽;不支援值選擇器" }, { "label": "編輯、插入與刪除列", "ios": "partial", - "iosNote": "列編輯器;需要主索引鍵" + "iosNote": "列編輯器;需要主索引鍵", + "linux": "partial", + "linuxNote": "需要主索引鍵" }, { "label": "暫存編輯、Preview SQL 與復原", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "暫存編輯與復原;不支援 Preview SQL" }, { "label": "Safe Mode", "ios": "partial", - "iosNote": "{iosSafeModeLevels}" + "iosNote": "{iosSafeModeLevels}", + "linux": "partial", + "linuxNote": "{linuxSafeModeLevels}" }, { "label": "寫入前要求 Touch ID 或密碼", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Rewind", "paid": "data-rewind", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "MongoDB 文件與 Redis 鍵", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "Redis 鍵僅能透過指令修改" } ], "limits": [ diff --git a/resources/data/content/zh-Hant/features/import-export.json b/resources/data/content/zh-Hant/features/import-export.json index a3171cf2..3f7acb4c 100644 --- a/resources/data/content/zh-Hant/features/import-export.json +++ b/resources/data/content/zh-Hant/features/import-export.json @@ -136,41 +136,52 @@ { "label": "將檔案匯入資料表", "ios": "partial", - "iosNote": "透過捷徑從 JSON 或 CSV 匯入" + "iosNote": "透過捷徑從 JSON 或 CSV 匯入", + "linux": "partial", + "linuxNote": "CSV 匯入現有資料表,以及 SQL 檔案" }, { "label": "已儲存對應、Match by Name 與 Match by Position", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "將資料表和結果匯出為檔案", "ios": "partial", - "iosNote": "複製或分享為 JSON、CSV 或 SQL INSERT" + "iosNote": "複製或分享為 JSON、CSV 或 SQL INSERT", + "linux": "yes" }, { "label": "{exportPluginFormats} 匯出外掛程式", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Data Files 視窗", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Backup Dump 與 Restore Dump", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "無需外部工具的 SQL 傾印" }, { "label": "Server-Side Export", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "SQLite 與 DuckDB 檔案", "ios": "partial", - "iosNote": "裝置上的檔案,或記憶體中的 DuckDB" + "iosNote": "裝置上的檔案,或記憶體中的 DuckDB", + "linux": "yes" }, { "label": "透過 SSH 存取 SQLite 檔案", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hant/features/querying.json b/resources/data/content/zh-Hant/features/querying.json index 733604f4..1a9840da 100644 --- a/resources/data/content/zh-Hant/features/querying.json +++ b/resources/data/content/zh-Hant/features/querying.json @@ -149,46 +149,58 @@ { "label": "支援自動完成與格式化的 SQL 編輯器", "ios": "partial", - "iosNote": "語法醒目提示、Run 與 Stop;不支援自動完成" + "iosNote": "語法醒目提示、Run 與 Stop;不支援自動完成", + "linux": "yes" }, { "label": "查詢參數", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "查詢歷史", "ios": "partial", - "iosNote": "裝置上最近 {historyEntriesIos} 筆查詢" + "iosNote": "裝置上最近 {historyEntriesIos} 筆查詢", + "linux": "yes" }, { "label": "已儲存查詢與連結 SQL 資料夾", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "僅已儲存查詢" }, { "label": "Open Quickly", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "在側邊欄搜尋連線與資料表" }, { "label": "EXPLAIN 與 EXPLAIN Compare", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Query Insights", "paid": "query-insights", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Result Charts", "paid": "result-charts", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "地圖檢視", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "複製或分享結果", - "ios": "yes" + "ios": "yes", + "linux": "yes" } ], "limits": [ diff --git a/resources/data/content/zh-Hant/features/schema.json b/resources/data/content/zh-Hant/features/schema.json index 264d2973..8f07f2bb 100644 --- a/resources/data/content/zh-Hant/features/schema.json +++ b/resources/data/content/zh-Hant/features/schema.json @@ -135,40 +135,50 @@ { "label": "含 DDL 預覽的結構編輯", "ios": "partial", - "iosNote": "欄位、索引與外部索引鍵唯讀" + "iosNote": "欄位、索引與外部索引鍵唯讀", + "linux": "yes" }, { "label": "New Table", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "清空與刪除資料表", - "ios": "yes" + "ios": "yes", + "linux": "yes" }, { "label": "ER 圖", - "ios": "no" + "ios": "no", + "linux": "yes" }, { "label": "常式、觸發程序與資料表資料夾", - "ios": "no" + "ios": "no", + "linux": "partial", + "linuxNote": "常式與觸發程序;不支援資料表資料夾" }, { "label": "Compare & Sync", "paid": "compare-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Copy To、Duplicate Database 與 Transfer To", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Users & Roles", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Server Dashboard 與資料表維護", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/content/zh-Hant/features/sync-and-teams.json b/resources/data/content/zh-Hant/features/sync-and-teams.json index 63405b5f..a6f1c8ef 100644 --- a/resources/data/content/zh-Hant/features/sync-and-teams.json +++ b/resources/data/content/zh-Hant/features/sync-and-teams.json @@ -150,46 +150,55 @@ "label": "iCloud Sync", "paid": "icloud-sync", "ios": "partial", - "iosNote": "連線、群組和標籤,免費" + "iosNote": "連線、群組和標籤,免費", + "linux": "no" }, { "label": "透過 iCloud 鑰匙圈同步密碼", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "選擇同步內容,將連線設為 Local only", "paid": "icloud-sync", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "啟用 iCloud Sync 時使用接力", "paid": "icloud-sync", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "匯出與匯入 .tablepro 檔案", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "含密碼的 Encrypted Export", "paid": "encrypted-export", - "ios": "yes" + "ios": "yes", + "linux": "no" }, { "label": "Linked Folders", "paid": "linked-folders", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Catalog", "paid": "team-catalog", - "ios": "no" + "ios": "no", + "linux": "no" }, { "label": "Team Library", "paid": "team-library", - "ios": "no" + "ios": "no", + "linux": "no" } ], "limits": [ diff --git a/resources/data/engines.json b/resources/data/engines.json index 30e38f57..1b9b16ca 100644 --- a/resources/data/engines.json +++ b/resources/data/engines.json @@ -48,6 +48,7 @@ } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["14", "15", "17", "18"], "evidence": "TableProApp/TablePro:linux/README.md:15" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -99,6 +100,7 @@ } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["8.0", "8.4", "9.7"], "evidence": "TableProApp/TablePro:linux/README.md:16" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -154,6 +156,7 @@ } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["2017", "2019", "2022", "2025"], "evidence": "TableProApp/TablePro:linux/README.md:18" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -213,6 +216,7 @@ { "id": "attached-files-not-listed", "value": null, "evidence": "TableProApp/docs:databases/sqlite.mdx:52" } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["3.37.0+"], "evidence": "TableProApp/TablePro:linux/README.md:22" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -262,6 +266,7 @@ { "id": "no-pipeline-builder", "value": null, "evidence": "TableProApp/docs:databases/mongodb.mdx:237" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["7.0", "8.0"], "evidence": "TableProApp/TablePro:linux/README.md:25" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -326,6 +331,7 @@ } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["7.4", "8.10"], "evidence": "TableProApp/TablePro:linux/README.md:24" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -374,6 +380,7 @@ { "id": "roles-listed-as-users", "value": null, "evidence": "TableProApp/docs:databases/mariadb.mdx:38" } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["10.11", "11.4", "12.3"], "evidence": "TableProApp/TablePro:linux/README.md:17" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -423,6 +430,7 @@ { "id": "structure-sync-same-engine", "value": null, "evidence": "TableProApp/docs:databases/tidb.mdx:44" } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "untested", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:31" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -472,6 +480,7 @@ { "id": "structure-sync-same-engine", "value": null, "evidence": "TableProApp/docs:databases/oceanbase.mdx:49" } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "untested", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:31" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -522,6 +531,7 @@ { "id": "not-on-ios", "value": null, "evidence": "TableProApp/docs:databases/databend.mdx:51" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "untested", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:31" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -579,6 +589,7 @@ } ], "ios": { "inPicker": false, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "untested", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:31" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -631,6 +642,7 @@ } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "untested", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:31" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -680,6 +692,7 @@ { "id": "cancel-has-no-effect", "value": null, "evidence": "TableProApp/docs:databases/pglite.mdx:52" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "untested", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:31" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -738,6 +751,7 @@ } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "blocked", "tested": [], "evidence": "TableProApp/TablePro:linux/ROADMAP.md:213" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -796,6 +810,7 @@ } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["26.3", "26.8"], "evidence": "TableProApp/TablePro:linux/README.md:19" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -849,6 +864,7 @@ { "id": "remote-quack-experimental", "value": null, "evidence": "TableProApp/docs:databases/duckdb.mdx:47" } ], "ios": { "inPicker": true, "openable": true, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["2.0.0"], "evidence": "TableProApp/TablePro:linux/README.md:23" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -899,6 +915,7 @@ { "id": "no-views-or-types-in-sidebar", "value": null, "evidence": "TableProApp/docs:databases/cassandra.mdx:103" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["4.0", "5.0"], "evidence": "TableProApp/TablePro:linux/README.md:20" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -944,6 +961,7 @@ "bundledVersion": null, "limits": [], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "tested", "tested": ["2025.1", "2026.1"], "evidence": "TableProApp/TablePro:linux/README.md:21" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -992,6 +1010,7 @@ { "id": "no-dax-or-truncate", "value": null, "evidence": "TableProApp/docs:databases/dynamodb.mdx:185,188" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1040,6 +1059,7 @@ { "id": "no-transactions", "value": null, "evidence": "TableProApp/docs:databases/bigquery.mdx:85" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1091,6 +1111,7 @@ } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1142,6 +1163,7 @@ } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1190,6 +1212,7 @@ { "id": "no-embedded-replicas", "value": null, "evidence": "TableProApp/docs:databases/libsql.mdx:75" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1241,6 +1264,7 @@ } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1289,6 +1313,7 @@ { "id": "aliases-not-listed", "value": null, "evidence": "TableProApp/docs:databases/elasticsearch.mdx:89" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1338,6 +1363,7 @@ { "id": "watch-collects-until-timeout", "value": null, "evidence": "TableProApp/docs:databases/etcd.mdx:81" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1387,6 +1413,7 @@ { "id": "approximate-counts", "value": null, "evidence": "TableProApp/docs:databases/kafka.mdx:148" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1435,6 +1462,7 @@ { "id": "no-live-queries", "value": null, "evidence": "TableProApp/docs:databases/surrealdb.mdx:105" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1483,6 +1511,7 @@ { "id": "foreign-keys-not-listed", "value": null, "evidence": "TableProApp/docs:databases/teradata.mdx:65" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1531,6 +1560,7 @@ { "id": "no-kerberos-or-oauth", "value": null, "evidence": "TableProApp/docs:databases/trino.mdx:112" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1583,6 +1613,7 @@ { "id": "needs-ledger-tool", "value": null, "evidence": "TableProApp/docs:databases/beancount.mdx:8" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1630,6 +1661,7 @@ { "id": "structure-read-only", "value": null, "evidence": "TableProApp/docs:databases/spanner.mdx:101" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1677,6 +1709,7 @@ { "id": "api-keys-as-users", "value": null, "evidence": "TableProApp/docs:databases/typesense.mdx:35,95" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1724,6 +1757,7 @@ { "id": "graphql-reads-rest-writes", "value": null, "evidence": "TableProApp/docs:databases/weaviate.mdx:8" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1773,6 +1807,7 @@ { "id": "billed-per-scan", "value": null, "evidence": "TableProApp/docs:databases/cloudflare-r2-sql.mdx:8,81" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1820,6 +1855,7 @@ { "id": "no-tls", "value": null, "evidence": "TableProApp/docs:databases/dameng.mdx:47" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } }, { @@ -1868,6 +1904,7 @@ { "id": "unsupported-auth-methods", "value": null, "evidence": "TableProApp/docs:databases/sap-hana.mdx:56" } ], "ios": { "inPicker": false, "openable": false, "appearsAfterSync": true }, + "linux": { "support": "none", "tested": [], "evidence": "TableProApp/TablePro:linux/README.md:7" }, "verified": { "macTag": "v0.82.0", "iosCommit": "232e8dae6", "docsCommit": "d735bb755", "date": "2026-10-19" } } ] diff --git a/resources/data/facts.json b/resources/data/facts.json index 32627f16..bad242c2 100644 --- a/resources/data/facts.json +++ b/resources/data/facts.json @@ -72,7 +72,7 @@ "evidence": "GitHub API, repos/TableProApp/TablePro created_at 2025-12-17T01:01:38Z" }, "ai": { - "platforms": ["mac"], + "platforms": ["mac", "linux"], "providers": [ "GitHub Copilot", "ChatGPT", @@ -89,10 +89,14 @@ "OpenCode Zen", "OpenAI-compatible" ], - "evidence": "TablePro/Models/AI/AIModels.swift:10-45 @v0.77.0" + "evidence": "TablePro/Models/AI/AIModels.swift:10-45 @v0.77.0", + "linux": { + "providers": ["Anthropic", "OpenAI", "Ollama", "OpenAI-compatible"], + "evidence": "linux/crates/app/src/ui/assistant/mod.rs:34-36; linux/README.md:533-541 @3d95564ff" + } }, "mcp": { - "platforms": ["mac"], + "platforms": ["mac", "linux"], "host": "127.0.0.1", "defaultPort": 23508, "enabledByDefault": false, @@ -120,7 +124,21 @@ "Goose" ] }, - "evidence": "TablePro/Views/Settings/Components/IntegrationClient.swift:4-7; docs/external-api/mcp-clients.mdx:3; docs/external-api/mcp-tools.mdx:20-30 @v0.77.0" + "evidence": "TablePro/Views/Settings/Components/IntegrationClient.swift:4-7; docs/external-api/mcp-clients.mdx:3; docs/external-api/mcp-tools.mdx:20-30 @v0.77.0", + "linux": { + "tools": [ + "list_connections", + "connect", + "list_schemas", + "list_tables", + "describe_table", + "get_table_ddl", + "browse_table", + "count_rows", + "execute_query" + ], + "evidence": "core/crates/mcp/src/tool_server.rs:50-58; linux/README.md:559,582 @3d95564ff" + } }, "safeMode": { "mac": { @@ -143,6 +161,15 @@ ], "default": "off", "evidence": "Packages/TableProCore/Sources/TableProModels/SafeModeLevel.swift:9-33 @232e8dae6" + }, + "linux": { + "levels": [ + { "id": "off", "name": "Off" }, + { "id": "confirm_writes", "name": "Confirm Writes" }, + { "id": "read_only", "name": "Read-Only" } + ], + "default": "off", + "evidence": "core/crates/core/src/safe_mode_level.rs:9-16; linux/crates/app/src/ui/safe_mode.rs:5-7 @3d95564ff" } }, "filterOperators": { @@ -203,7 +230,11 @@ { "id": "xlsx", "name": "XLSX", "exceptEngines": [] }, { "id": "sql", "name": "SQL", "exceptEngines": ["mongodb", "redis"] } ], - "evidence": "Plugins/{CSV,JSON,XLSX,SQL}ImportPlugin @v0.77.0; SQLImportPlugin.swift:22 excludedDatabaseTypeIds" + "evidence": "Plugins/{CSV,JSON,XLSX,SQL}ImportPlugin @v0.77.0; SQLImportPlugin.swift:22 excludedDatabaseTypeIds", + "linux": { + "formats": ["csv", "sql"], + "evidence": "linux/ROADMAP.md:60,129; linux/data/resources/ui/import-csv-dialog.ui:36-43 @3d95564ff" + } }, "export": { "formats": [ @@ -217,7 +248,11 @@ { "id": "parquet", "name": "Parquet", "engines": null, "exceptEngines": [], "via": "plugin" }, { "id": "mql", "name": "MQL", "engines": ["mongodb"], "exceptEngines": [], "via": null } ], - "evidence": "TablePro/Core/Services/Export/ExportFormatCatalog.swift:16-18; Plugins/SQLExportPlugin/SQLExportPlugin.swift:20 @v0.77.0" + "evidence": "TablePro/Core/Services/Export/ExportFormatCatalog.swift:16-18; Plugins/SQLExportPlugin/SQLExportPlugin.swift:20 @v0.77.0", + "linux": { + "formats": ["csv", "json", "sql", "xlsx", "markdown", "html", "xml"], + "evidence": "linux/crates/app/src/ui/export/dialog.rs:49-67; linux/ROADMAP.md:120 @3d95564ff" + } }, "backup": { "tools": [ @@ -322,6 +357,20 @@ "platform": "ios", "evidence": "TableProMobile/TableProMobile/ViewModels/QueryEditorViewModel.swift:18 @232e8dae6" }, + "resultRowCapLinux": { + "value": 10000, + "max": null, + "unit": "rows", + "platform": "linux", + "evidence": "core/crates/core/src/row_limit.rs:14; linux/ROADMAP.md:102 @3d95564ff" + }, + "runRowCapLinux": { + "value": 100000, + "max": null, + "unit": "rows", + "platform": "linux", + "evidence": "linux/crates/app/src/ui/editor/script_runner.rs:34-37; linux/ROADMAP.md:102 @3d95564ff" + }, "rewindDays": { "value": 7, "max": null, diff --git a/resources/js/components/features/README.md b/resources/js/components/features/README.md index 9561677d..9b36e068 100644 --- a/resources/js/components/features/README.md +++ b/resources/js/components/features/README.md @@ -68,16 +68,21 @@ querying holds EXPLAIN Compare and Query Insights. Every section needs text or b ### Where it works (`availability`) ```jsonc -{ "label": "Query Insights", "paid": "query-insights", "ios": "no" } -{ "label": "Query history", "ios": "partial", "iosNote": "The last {historyEntriesIos} queries on the device" } -{ "label": "Copy or share results", "ios": "yes" } -{ "label": "Live Activities", "mac": "no", "ios": "yes" } // "mac" defaults to "yes" +{ "label": "Query Insights", "paid": "query-insights", "ios": "no", "linux": "no" } +{ "label": "Query history", "ios": "partial", "iosNote": "The last {historyEntriesIos} queries on the device", "linux": "yes" } +{ "label": "Copy or share results", "ios": "yes", "linux": "yes" } +{ "label": "Live Activities", "mac": "no", "ios": "yes", "linux": "no" } // "mac" defaults to "yes" +{ "label": "Safe Mode", "ios": "partial", "iosNote": "{iosSafeModeLevels}", "linux": "partial", "linuxNote": "{linuxSafeModeLevels}" } ``` The Mac column shows the plan from `paid` (or "Free"); the iPhone and iPad column shows "Free" for `yes`, a dash for `no`, and `iosNote` for `partial` (`iosNote` is required exactly when `ios` is `partial`). The iPhone and iPad app gates nothing, so it has no plan column. Device names come from `platforms.json`. +`linux` is required on every row, and `linuxNote` exactly when it is `partial`, in the same short style as `iosNote`. +Decide `linux` in English: rows align by index, and every locale carries the same value. None of the paid features +exists in the Linux app, so a row with `paid` is `no` there. The page does not show the Linux column yet. + ### Links Exactly one of `href` (a page on this site: root-relative, English form, it is localized for you) or `docs` (a diff --git a/resources/js/components/features/types.ts b/resources/js/components/features/types.ts index 4043dffa..b45c6da7 100644 --- a/resources/js/components/features/types.ts +++ b/resources/js/components/features/types.ts @@ -68,6 +68,8 @@ export interface FeatureSection extends FeatureBlock { export type IosAvailability = 'yes' | 'no' | 'partial'; +export type LinuxAvailability = 'yes' | 'no' | 'partial'; + /** A row of "Where it works": one capability, its plan on the Mac and its state on iPhone and iPad. */ export interface AvailabilityRow { label: string; @@ -78,6 +80,10 @@ export interface AvailabilityRow { ios: IosAvailability; /** Required when `ios` is `partial`: what exists there. */ iosNote?: string; + // The same in every locale; no paid feature exists on Linux yet. + linux: LinuxAvailability; + // Present exactly when `linux` is `partial`. + linuxNote?: string; } export interface FeaturePageContent { diff --git a/resources/js/lib/data/engines.ts b/resources/js/lib/data/engines.ts index 3696f271..61612300 100644 --- a/resources/js/lib/data/engines.ts +++ b/resources/js/lib/data/engines.ts @@ -90,6 +90,16 @@ export interface EngineIos { appearsAfterSync: boolean; } +// untested: a Linux driver speaks its protocol but no suite runs on it. blocked: no usable client library. +export type LinuxSupport = 'tested' | 'untested' | 'blocked' | 'none'; + +export interface EngineLinux { + support: LinuxSupport; + // Server releases as the app's linux/README.md lists them; empty unless support is tested. + tested: string[]; + evidence: string; +} + export interface Engine { id: string; /** The app's `DatabaseType` raw value, spaces included. */ @@ -128,5 +138,6 @@ export interface Engine { bundledVersion: string | null; limits: EngineLimit[]; ios: EngineIos; + linux: EngineLinux; verified: { macTag: string; iosCommit: string; docsCommit: string; date: string }; } diff --git a/resources/js/lib/data/facts.ts b/resources/js/lib/data/facts.ts index b4090bcf..10f26aea 100644 --- a/resources/js/lib/data/facts.ts +++ b/resources/js/lib/data/facts.ts @@ -43,6 +43,12 @@ export interface SyncCategory { sinceAppVersion: string | null; } +// Ids from the Mac list beside it, which holds each format's name. +export interface LinuxFormats { + formats: string[]; + evidence: string; +} + export interface Limit { value: number; max: number | null; @@ -81,7 +87,12 @@ export interface FactsData { evidence: string; }; openSource: { license: string; repositoryCreatedAt: string; evidence: string }; - ai: { platforms: PlatformId[]; providers: string[]; evidence: string }; + ai: { + platforms: PlatformId[]; + providers: string[]; + evidence: string; + linux: { providers: string[]; evidence: string }; + }; mcp: { platforms: PlatformId[]; host: string; @@ -91,17 +102,20 @@ export interface FactsData { toolGroups: string[]; clients: { setupSheet: string[]; bridge: string[] }; evidence: string; + // Tool names as the Linux server answers them, not group ids. + linux: { tools: string[]; evidence: string }; }; safeMode: { mac: { levels: SafeModeLevel[]; default: string; evidence: string }; ios: { levels: SafeModeLevel[]; default: string; evidence: string }; + linux: { levels: SafeModeLevel[]; default: string; evidence: string }; }; filterOperators: { mac: string[]; ios: string[]; evidence: string }; /** Apps whose connections TablePro imports. `id` matches a `comparisons.json` product where one exists. */ connectionImport: { id: string; app: string; passwords: boolean; format: string | null }[]; /** `exceptEngines`: engine ids the format's import plugin refuses (SQL on MongoDB and Redis). */ - dataImport: { formats: { id: string; name: string; exceptEngines: string[] }[]; evidence: string }; - export: { formats: ExportFormat[]; evidence: string }; + dataImport: { formats: { id: string; name: string; exceptEngines: string[] }[]; evidence: string; linux: LinuxFormats }; + export: { formats: ExportFormat[]; evidence: string; linux: LinuxFormats }; backup: { tools: BackupTool[]; serverSideExport: string[]; evidence: string }; sync: { mac: { categories: SyncCategory[]; enabledByDefault: boolean; evidence: string }; diff --git a/tests/Feature/Data/EnginesDataTest.php b/tests/Feature/Data/EnginesDataTest.php index 27091813..e3e88317 100644 --- a/tests/Feature/Data/EnginesDataTest.php +++ b/tests/Feature/Data/EnginesDataTest.php @@ -23,7 +23,7 @@ function enginesMacRelease(): string expect($engine)->toHaveKeys([ 'id', 'appTypeId', 'name', 'page', 'slug', 'parent', 'anchor', 'category', 'featured', 'meta', 'distribution', 'driverPlugin', 'sinceAppVersion', 'minAppVersion', 'state', 'queryLanguage', 'defaultPort', - 'connectionMode', 'icon', 'monogram', 'docsSlug', 'capabilities', 'versionFloor', 'limits', 'ios', + 'connectionMode', 'icon', 'monogram', 'docsSlug', 'capabilities', 'versionFloor', 'limits', 'ios', 'linux', ]) ->and($id)->toMatch('/^[a-z0-9]+(-[a-z0-9]+)*$/') ->and($engine['name'])->toBeString()->not->toBeEmpty() @@ -111,6 +111,26 @@ function enginesMacRelease(): string expect($inPicker)->toEqualCanonicalizing(collect($platforms)->firstWhere('id', 'ios')['iosEngines']); }); +it('states where each engine runs on Linux, with evidence from the app', function (): void { + foreach (enginesJson() as $engine) { + $linux = $engine['linux']; + $id = $engine['id']; + + expect($linux)->toHaveKeys(['support', 'tested', 'evidence']) + ->and($linux['support'])->toBeIn(['tested', 'untested', 'blocked', 'none'], "{$id}.linux.support") + ->and($linux['tested'])->toBeArray() + ->and($linux['tested'] !== [])->toBe($linux['support'] === 'tested', "{$id}: tested releases exactly when support is tested") + ->and($linux['evidence'])->toMatch('/^TableProApp\/TablePro:\S+:\d+/', "{$id}.linux.evidence"); + + foreach ($linux['tested'] as $release) { + expect($release)->toBeString()->not->toBeEmpty(); + } + } + + // linux/README.md:15-25 tests eleven products; any other count means one side moved without the other. + expect(collect(enginesJson())->where('linux.support', 'tested')->count())->toBe(11); +}); + it('links each engine to a docs page of its own or of its driver sibling', function (): void { foreach (enginesJson() as $engine) { if ($engine['docsSlug'] !== null) { diff --git a/tests/Feature/Data/FactsDataTest.php b/tests/Feature/Data/FactsDataTest.php index bebb3ba9..271b75bc 100644 --- a/tests/Feature/Data/FactsDataTest.php +++ b/tests/Feature/Data/FactsDataTest.php @@ -58,12 +58,16 @@ function factsUnique(array $list, string $label): void $facts = factsJson(); factsUnique($facts['ai']['providers'], 'ai.providers'); + factsUnique($facts['ai']['linux']['providers'], 'ai.linux.providers'); factsUnique($facts['mcp']['toolGroups'], 'mcp.toolGroups'); + factsUnique($facts['mcp']['linux']['tools'], 'mcp.linux.tools'); factsUnique($facts['filterOperators']['mac'], 'filterOperators.mac'); factsUnique($facts['filterOperators']['ios'], 'filterOperators.ios'); factsUnique(array_column($facts['connectionImport'], 'id'), 'connectionImport'); factsUnique(array_column($facts['dataImport']['formats'], 'id'), 'dataImport.formats'); factsUnique(array_column($facts['export']['formats'], 'id'), 'export.formats'); + factsUnique($facts['dataImport']['linux']['formats'], 'dataImport.linux.formats'); + factsUnique($facts['export']['linux']['formats'], 'export.linux.formats'); factsUnique(array_column($facts['backup']['tools'], 'id'), 'backup.tools'); factsUnique($facts['sync']['neverSynced'], 'sync.neverSynced'); }); @@ -74,6 +78,31 @@ function factsUnique(array $list, string $label): void } }); +it('states the Linux app in the shapes the Mac entries use', function (): void { + $facts = factsJson(); + $platforms = array_column(factsData('platforms.json')['platforms'], 'id'); + + foreach (['ai', 'mcp'] as $section) { + expect($facts[$section]['platforms'])->toContain('linux') + ->and(array_diff($facts[$section]['platforms'], $platforms))->toBe([], "{$section}.platforms names an unknown platform"); + } + + foreach (['dataImport', 'export'] as $section) { + expect(array_diff($facts[$section]['linux']['formats'], array_column($facts[$section]['formats'], 'id'))) + ->toBe([], "{$section}.linux names a format the Mac list does not have"); + } + + expect($facts['safeMode']['linux']['levels'])->not->toBeEmpty(); + + foreach ([$facts['ai']['linux'], $facts['mcp']['linux'], $facts['safeMode']['linux'], $facts['dataImport']['linux'], $facts['export']['linux']] as $entry) { + expect($entry['evidence'])->toBeString()->not->toBeEmpty(); + } + + foreach ($facts['limits'] as $id => $limit) { + expect(str_ends_with($id, 'Linux'))->toBe($limit['platform'] === 'linux', "limits.{$id}: a Linux limit's id ends in Linux"); + } +}); + it('refers only to engines, compared products and platforms the other data files have', function (): void { $facts = factsJson(); $engines = array_column(factsData('engines.json'), 'id'); diff --git a/tests/Feature/Pages/FeaturesTest.php b/tests/Feature/Pages/FeaturesTest.php index 4aaefd74..fe3107ff 100644 --- a/tests/Feature/Pages/FeaturesTest.php +++ b/tests/Feature/Pages/FeaturesTest.php @@ -2,6 +2,7 @@ use App\Support\Content\Slugs\FeatureSlugs; use App\Support\Features\FeatureFacts; +use App\Support\Localization\Locales; use Illuminate\Support\Facades\File; use Inertia\Testing\AssertableInertia; @@ -89,6 +90,38 @@ function featuresBlocks(array $content): array } })->with(FeatureSlugs::ALL); +it('marks each availability row on Linux the same in every language', function (string $slug): void { + $english = featuresData("content/en/features/{$slug}.json")['availability']; + $paidPlatforms = array_column(featuresData('paid-features.json'), 'platforms', 'id'); + + foreach (Locales::codes() as $locale) { + $path = "content/{$locale}/features/{$slug}.json"; + + if (! File::exists(resource_path("data/{$path}"))) { + continue; + } + + $rows = featuresData($path)['availability']; + + expect(array_column($rows, 'linux'))->toBe(array_column($english, 'linux'), "{$locale}/{$slug}: a row's Linux value differs from English"); + + foreach ($rows as $index => $row) { + $where = "{$locale}/{$slug} availability.{$index}"; + + expect($row['linux'] ?? null)->toBeIn(['yes', 'partial', 'no'], "{$where}.linux") + ->and(array_key_exists('linuxNote', $row))->toBe($row['linux'] === 'partial', "{$where}: linuxNote exactly when partial"); + + if (array_key_exists('linuxNote', $row)) { + expect(trim($row['linuxNote']))->not->toBe('', "{$where}.linuxNote"); + } + + if (isset($row['paid']) && ! in_array('linux', $paidPlatforms[$row['paid']], true)) { + expect($row['linux'])->toBe('no', "{$where}: {$row['paid']} is not in the Linux app"); + } + } + } +})->with(FeatureSlugs::ALL); + it('quotes limits, the device limit and Safe Mode levels from the data files', function (): void { $facts = (new FeatureFacts())->all(); $data = featuresData('facts.json'); @@ -99,7 +132,10 @@ function featuresBlocks(array $content): array ->and($facts['filterOperatorCount']['value'])->toBe(count($data['filterOperators']['mac'])) ->and($facts['deviceLimit'])->toBe(['kind' => 'number', 'value' => featuresData('pricing.json')['license']['deviceLimit']]) ->and($facts['macSafeModeLevels']['items'])->toBe(array_column($safeMode['mac']['levels'], 'name')) - ->and($facts['iosSafeModeDefault']['value'])->toBe(array_column($safeMode['ios']['levels'], 'name', 'id')[$safeMode['ios']['default']]); + ->and($facts['iosSafeModeDefault']['value'])->toBe(array_column($safeMode['ios']['levels'], 'name', 'id')[$safeMode['ios']['default']]) + ->and($facts['linuxSafeModeLevels']['items'])->toBe(array_column($safeMode['linux']['levels'], 'name')) + ->and($facts['aiProvidersLinux']['items'])->toBe($data['ai']['linux']['providers']) + ->and($facts['mcpToolCountLinux']['value'])->toBe(count($data['mcp']['linux']['tools'])); }); it('derives every engine list from engines.json capabilities', function (): void { From 0bb56bb82102e0e250e78b80c8b92e73355e1e80 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 6 Nov 2026 22:37:31 +0700 Subject: [PATCH 4/5] feat: recognise a Linux desktop, and a JSON-LD node for the Linux app --- resources/js/lib/device.ts | 32 +++++++++---------------- resources/js/lib/structured-data.ts | 37 +++++++++++++++++++++++++++++ tests/js/device.test.ts | 11 +++++++-- tests/js/structured-data.test.ts | 24 +++++++++++++++++++ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/resources/js/lib/device.ts b/resources/js/lib/device.ts index 15b2d16b..0bfbafd7 100644 --- a/resources/js/lib/device.ts +++ b/resources/js/lib/device.ts @@ -1,37 +1,27 @@ -/** - * What the download page may infer about the reader's device (architecture - * §1.13). The page never acts on it: the Mac DMG is a server-rendered link, - * and nothing starts a download on its own. The result only decides whether - * the App Store card comes first, and whether the Mac card says to open the - * page on a Mac. - * - * An iPhone or iPad is never a Mac. iPhone user agents contain "like Mac OS - * X", and iPadOS Safari sends a desktop "Macintosh" user agent by default, so - * a "Macintosh" with more than one touch point is an iPad. - * - * Pure, with no imports, so `node --test` loads it directly - * (tests/js/device.test.ts). - */ +// What the download page may infer about the reader's device (architecture §1.13). It never acts on it: every download +// is a server-rendered link and nothing starts on its own. The result only decides which card is marked and leads. +// Pure, with no imports, so `node --test` loads it directly (tests/js/device.test.ts). -export type DeviceKind = 'mac' | 'ios' | 'other'; +export type DeviceKind = 'mac' | 'ios' | 'linux' | 'other'; -/** - * `ios` for an iPhone, iPad or iPod (including iPadOS's desktop user agent), - * `mac` for a Mac, `other` for anything else. - */ export function classifyDevice(userAgent: string, maxTouchPoints: number): DeviceKind { if (/iPhone|iPad|iPod/.test(userAgent)) { return 'ios'; } + // iPadOS Safari sends a desktop "Macintosh" user agent; a touch screen gives it away. if (/Macintosh/.test(userAgent)) { return maxTouchPoints > 1 ? 'ios' : 'mac'; } - return 'other'; + // Android and ChromeOS also say Linux or X11, and neither runs the desktop packages. + if (/Android|CrOS/.test(userAgent)) { + return 'other'; + } + + return /Linux|X11/.test(userAgent) ? 'linux' : 'other'; } -/** The App Store card leads on an iPhone or iPad, and only there. */ export function appStoreFirst(device: DeviceKind): boolean { return device === 'ios'; } diff --git a/resources/js/lib/structured-data.ts b/resources/js/lib/structured-data.ts index 407ba211..cf39e5d6 100644 --- a/resources/js/lib/structured-data.ts +++ b/resources/js/lib/structured-data.ts @@ -74,6 +74,10 @@ export function iosAppId(baseUrl: string): string { return `${origin(baseUrl)}/#ios-app`; } +export function linuxAppId(baseUrl: string): string { + return `${origin(baseUrl)}/#linux-app`; +} + export function websiteId(baseUrl: string): string { return `${origin(baseUrl)}/#website`; } @@ -280,6 +284,39 @@ export function iosAppNode(context: PageContext, input: IosAppInput): JsonLdNode }; } +export interface LinuxAppInput { + alternateName: string; + // "Linux": schema.org has no list of distributions, so they go in softwareRequirements. + operatingSystem: string; + requirements: string; + installUrl: string; + licenseUrl: string; + currency: string; + description?: string; + version?: string | null; +} + +// Free to install and use; a license adds nothing on Linux yet, so it carries only the zero-price offer. +export function linuxAppNode(context: PageContext, input: LinuxAppInput): JsonLdNode { + return { + '@type': 'SoftwareApplication', + '@id': linuxAppId(context.baseUrl), + name: 'TablePro', + alternateName: input.alternateName, + ...(input.description ? { description: input.description } : {}), + applicationCategory: 'DeveloperApplication', + operatingSystem: input.operatingSystem, + softwareRequirements: input.requirements, + installUrl: input.installUrl, + license: input.licenseUrl, + isAccessibleForFree: true, + offers: [offerNode({ name: input.alternateName, price: 0, currency: input.currency })], + inLanguage: context.inLanguage, + publisher: reference(organizationId(context.baseUrl)), + ...(input.version ? { softwareVersion: input.version } : {}), + }; +} + export function breadcrumbNode(context: PageContext, pageUrl: string, crumbs: readonly Crumb[]): JsonLdNode { return { '@type': 'BreadcrumbList', diff --git a/tests/js/device.test.ts b/tests/js/device.test.ts index aad41aee..c2f74446 100644 --- a/tests/js/device.test.ts +++ b/tests/js/device.test.ts @@ -36,10 +36,16 @@ test('a Mac browser is a Mac, whatever chip its user agent claims', () => { assert.equal(classifyDevice(UA.safariMac, 1), 'mac'); }); -test('Windows, Linux and Android are neither', () => { +test('a Linux desktop is Linux, on Wayland or X11', () => { + assert.equal(classifyDevice(UA.linux, 0), 'linux'); + assert.equal(classifyDevice('Mozilla/5.0 (X11; Ubuntu; Linux aarch64; rv:146.0) Gecko/20100101 Firefox/146.0', 0), 'linux'); + assert.equal(classifyDevice('Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/146.0', 0), 'linux'); +}); + +test('Windows, Android and ChromeOS are none of them', () => { assert.equal(classifyDevice(UA.chromeWindows, 0), 'other'); - assert.equal(classifyDevice(UA.linux, 0), 'other'); assert.equal(classifyDevice(UA.android, 5), 'other'); + assert.equal(classifyDevice('Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36', 0), 'other'); assert.equal(classifyDevice('', 0), 'other'); }); @@ -47,6 +53,7 @@ test('the App Store card leads only on an iPhone or iPad', () => { assert.equal(appStoreFirst('ios'), true); assert.equal(appStoreFirst('mac'), false); assert.equal(appStoreFirst('other'), false); + assert.equal(appStoreFirst('linux'), false); }); // The head script cannot import this module, so it repeats the rule. diff --git a/tests/js/structured-data.test.ts b/tests/js/structured-data.test.ts index a52044db..4d2189c3 100644 --- a/tests/js/structured-data.test.ts +++ b/tests/js/structured-data.test.ts @@ -241,3 +241,27 @@ test('the about page is an AboutPage about the organization', () => { assert.deepEqual(page.about, { '@id': 'https://tablepro.app/#organization' }); assert.equal(page.inLanguage, 'vi'); }); + +test('the Linux app is its own free SoftwareApplication, never the Mac node', async () => { + const { linuxAppNode, linuxAppId, macAppId } = await import('../../resources/js/lib/structured-data.ts'); + const node = linuxAppNode( + { baseUrl: 'https://tablepro.app', inLanguage: 'vi' }, + { + alternateName: 'TablePro for Linux', + operatingSystem: 'Linux', + requirements: 'Ubuntu 26.04, Fedora 44 or Flatpak; x86_64, arm64', + installUrl: 'https://tablepro.app/download#linux', + licenseUrl: 'https://github.com/TableProApp/TablePro/blob/main/LICENSE', + currency: 'USD', + version: '0.1.0-beta.1', + }, + ); + + assert.equal(node['@id'], 'https://tablepro.app/#linux-app'); + assert.notEqual(linuxAppId('https://tablepro.app'), macAppId('https://tablepro.app')); + assert.equal(node['@type'], 'SoftwareApplication'); + assert.equal(node.operatingSystem, 'Linux'); + assert.equal(node.isAccessibleForFree, true); + assert.equal(node.softwareVersion, '0.1.0-beta.1'); + assert.deepEqual(node.offers, [{ '@type': 'Offer', name: 'TablePro for Linux', price: '0', priceCurrency: 'USD' }]); +}); From 534ffdca111890ad7fcdb42dec633b946cc1a324 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Fri, 6 Nov 2026 22:37:31 +0700 Subject: [PATCH 5/5] docs: the Linux platform record and release service --- CLAUDE.md | 2 +- docs/rebuild/design/architecture.md | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d77f05a6..c3438c2f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -115,7 +115,7 @@ php artisan og:generate --type=compare --locale=all # types: site|blog|database # commit public/og; delete a retired page's card (Seo/OgCardsTest fails on a missing or orphaned card) php artisan assets:generate # after editing resources/data/assets.json php artisan assets:generate --check # checks only frontend asset catalogs -php artisan release:check # network: compares platforms.json with GitHub, Sparkle, Homebrew, App Store +php artisan release:check # network: compares platforms.json with GitHub, Sparkle, Homebrew, App Store, and Linux's PPA, COPR and Flatpak once it ships ``` Run `vendor/bin/pint --dirty --format agent` after editing any PHP file. diff --git a/docs/rebuild/design/architecture.md b/docs/rebuild/design/architecture.md index 2241ce03..a0de81ed 100644 --- a/docs/rebuild/design/architecture.md +++ b/docs/rebuild/design/architecture.md @@ -456,7 +456,23 @@ unique; no prefix collides with a root slug. "price": { "amount": 0, "inAppPurchases": false }, "appLanguages": ["…"] }, - { "id": "linux", "status": "prototype" }, + { + "id": "linux", "status": "prototype", // carries its record before it ships; status alone decides whether it shows + "channel": "beta", // beta | stable: which repositories the commands name + "deviceNames": ["Linux"], + "distributions": [{ "id": "ubuntu", "name": "Ubuntu", "versions": ["26.04", "26.10"], "via": "ppa" }, + { "id": "fedora", "name": "Fedora", "versions": ["44", "45"], "via": "copr" }, + { "id": "other", "name": null, "versions": [], "via": "flatpak" }], + "desktop": { "name": "GNOME", "version": "50" }, // the .deb and .rpm floor; the Flatpak brings its runtime + "architectures": ["x86_64", "arm64"], + "destinations": [{ "kind": "ppa", "repository": "ppa:tablepro/tablepro-beta", "commands": ["…"], "update": "sudo apt upgrade" }, + { "kind": "copr", "repository": "@tablepro/tablepro-beta", "commands": ["…"], "update": "sudo dnf upgrade" }, + { "kind": "flatpak", "url": "https://flatpak.tablepro.app/beta-repo/…flatpakref", "commands": ["…"] }, + { "kind": "packages", "tagPrefix": "linux-v", "assets": [{ "format": "deb", "template": "tablepro-{version}-ubuntu-{series}-{arch}.deb", "series": ["…"], "arch": { "x86_64": "amd64", "arm64": "arm64" } }], + "checksums": "SHA256SUMS", "verify": "gh attestation verify {file} --repo TableProApp/TablePro" }], + "release": { "version": "0.1.0-beta.1", "build": null, "publishedAt": "2026-11-06" }, + "appLanguages": ["en", "vi"] + }, { "id": "windows", "status": "none" } ] } @@ -466,6 +482,12 @@ Values come from the GitHub releases, the appcast, the Homebrew cask and the App iOS 1.0 (build 22). The status word is `released`, as positioning §3.3 and §13 use it. +Linux has its own version line and its own release service (`LinuxReleaseService`): its GitHub releases are tagged +`linux-v*` and published with `--latest=false`, so the Mac's `releases/latest` lookup never sees them. The service +lists releases, keeps the newest on the channel (a beta channel takes pre-releases), reads each package's size and +SHA-256 digest, and stands in with the `release` above until GitHub has one. `release:refresh-linux` runs beside +`release:refresh`, and `release:check` compares the PPA, COPR and Flatpak repository once Linux is `released`. + Every phrase around these values is a catalog entry in the `platforms` namespace, never data: `requirement` ("{systems} {version} {releaseName} or later" / "… trở lên", with `releaseName` dropped when null), `architectures.{arm64,x86_64}` ("Apple silicon", "Intel"), and `app.{mac,ios}` ("Mac app" / "ứng dụng Mac"). The @@ -482,7 +504,9 @@ positioning tokens are derived from these values, never typed: - The statuses are in the enum. - A `released` platform has `deviceNames`, `requirements`, `destinations` and `release`. Any other platform has none - of them. + of them, except Linux, which carries its record from before it ships. +- Linux's repositories match its channel (`-beta` names on beta), every command starts with `sudo` or `flatpak`, and + every package template names `{version}` and `{arch}`. - The Mac `minVersion` is `13.0` with `releaseName` Ventura. Nothing in the file says 14 or Sonoma. - `universalBinary` is false. - The `iosEngines` ids exist in `engines.json` with `ios.inPicker`.