feat(apple): add Russian and Hindi app catalogs

This commit is contained in:
Vincent Koc
2026-06-26 15:28:40 -07:00
parent 4dcd95d1bc
commit 02046b665f
9 changed files with 221 additions and 0 deletions
+4
View File
@@ -836,6 +836,10 @@ jobs:
- name: Check Android app i18n resources
run: pnpm android:i18n:check
- name: Check Apple app i18n catalogs
if: needs.preflight.outputs.run_macos == 'true'
run: pnpm apple:i18n:check
checks-fast-core:
permissions:
contents: read
+118
View File
@@ -0,0 +1,118 @@
{
"sourceLanguage" : "en",
"strings" : {
"Agent" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Agent" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "एजेंट" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Агент" } }
}
},
"Cancel" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Cancel" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "रद्द करें" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Отмена" } }
}
},
"Chat" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Chat" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "चैट" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Чат" } }
}
},
"Close" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Close" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "बंद करें" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Закрыть" } }
}
},
"Connect" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Connect" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "कनेक्ट करें" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Подключить" } }
}
},
"Connect to a Gateway?" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Connect to a Gateway?" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "गेटवे से कनेक्ट करें?" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Подключиться к шлюзу?" } }
}
},
"Connecting…" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Connecting…" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "कनेक्ट हो रहा है…" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Подключение…" } }
}
},
"Control" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Control" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "कंट्रोल" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Управление" } }
}
},
"Done" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Done" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "पूर्ण" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Готово" } }
}
},
"Not now" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Not now" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "अभी नहीं" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Не сейчас" } }
}
},
"No gateways found yet. Make sure your gateway is running and Bonjour discovery is enabled." : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "No gateways found yet. Make sure your gateway is running and Bonjour discovery is enabled." } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "अभी कोई गेटवे नहीं मिला। सुनिश्चित करें कि आपका गेटवे चल रहा है और Bonjour खोज सक्षम है।" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Шлюзы пока не найдены. Убедитесь, что шлюз запущен и обнаружение Bonjour включено." } }
}
},
"Quick Setup" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Quick Setup" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "त्वरित सेटअप" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Быстрая настройка" } }
}
},
"Settings" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Settings" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "सेटिंग्स" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Настройки" } }
}
},
"Trust and connect" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Trust and connect" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "भरोसा करें और कनेक्ट करें" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Доверять и подключиться" } }
}
},
"Trust this gateway?" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Trust this gateway?" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "इस गेटवे पर भरोसा करें?" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Доверять этому шлюзу?" } }
}
},
"Writing" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Writing" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "लिख रहा है" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Пишет" } }
}
}
},
"version" : "1.0"
}
+2
View File
@@ -60,6 +60,8 @@ targets:
Release: Signing.xcconfig
sources:
- path: Sources
resources:
- path: Resources/Localizable.xcstrings
dependencies:
- target: OpenClawShareExtension
embed: true
+1
View File
@@ -63,6 +63,7 @@ let package = Package(
resources: [
.copy("Resources/OpenClaw.icns"),
.copy("Resources/DeviceModels"),
.process("Resources/Localizable.xcstrings"),
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
@@ -0,0 +1,34 @@
{
"sourceLanguage" : "en",
"strings" : {
"Logout" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Logout" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "लॉग आउट" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Выйти" } }
}
},
"Refresh" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Refresh" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "रिफ्रेश करें" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Обновить" } }
}
},
"Run now" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Run now" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "अभी चलाएँ" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Запустить сейчас" } }
}
},
"Save" : {
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Save" } },
"hi" : { "stringUnit" : { "state" : "translated", "value" : "सहेजें" } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Сохранить" } }
}
}
},
"version" : "1.0"
}
+1
View File
@@ -1959,6 +1959,7 @@
"native:i18n:check": "node --import tsx scripts/native-app-i18n.ts check",
"native:i18n:sync": "node --import tsx scripts/native-app-i18n.ts sync --write",
"android:i18n:check": "node --import tsx scripts/android-app-i18n.ts check",
"apple:i18n:check": "node --import tsx scripts/apple-app-i18n.ts check",
"ui:install": "node scripts/ui.js install",
"verify": "node scripts/verify.mjs"
},
+52
View File
@@ -0,0 +1,52 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const HERE = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(HERE, "..");
const CATALOGS = [
path.join(ROOT, "apps", "ios", "Resources", "Localizable.xcstrings"),
path.join(ROOT, "apps", "macos", "Sources", "OpenClaw", "Resources", "Localizable.xcstrings"),
];
type Catalog = {
sourceLanguage?: string;
strings?: Record<
string,
{
localizations?: Record<string, { stringUnit?: { value?: string } }>;
}
>;
};
export async function checkAppleAppI18n() {
let checked = 0;
for (const catalogPath of CATALOGS) {
const catalog = JSON.parse(await readFile(catalogPath, "utf8")) as Catalog;
if (catalog.sourceLanguage !== "en" || !catalog.strings) {
throw new Error(`invalid Apple string catalog: ${path.relative(ROOT, catalogPath)}`);
}
for (const [key, entry] of Object.entries(catalog.strings)) {
for (const locale of ["en", "ru", "hi"]) {
const value = entry.localizations?.[locale]?.stringUnit?.value?.trim();
if (!value) {
throw new Error(
`Apple catalog ${path.relative(ROOT, catalogPath)} is missing ${locale} for ${JSON.stringify(key)}`,
);
}
}
checked += 1;
}
}
process.stdout.write(
`apple-app-i18n: catalogs=${CATALOGS.length} keys=${checked} locales=ru,hi\n`,
);
}
if (process.argv[1] && import.meta.url === `file://${path.resolve(process.argv[1])}`) {
const [command] = process.argv.slice(2);
if (command !== "check") {
throw new Error("usage: node --import tsx scripts/apple-app-i18n.ts check");
}
await checkAppleAppI18n();
}
+1
View File
@@ -746,6 +746,7 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
["scripts/ci-changed-scope.mjs", ["src/scripts/ci-changed-scope.test.ts"]],
["scripts/ci-docker-pull-retry.sh", ["test/scripts/ci-docker-pull-retry.test.ts"]],
["scripts/control-ui-i18n.ts", ["test/scripts/control-ui-i18n.test.ts"]],
["scripts/apple-app-i18n.ts", ["test/scripts/apple-app-i18n.test.ts"]],
["scripts/native-app-i18n.ts", ["test/scripts/native-app-i18n.test.ts"]],
["scripts/android-app-i18n.ts", ["test/scripts/android-app-i18n.test.ts"]],
[
+8
View File
@@ -0,0 +1,8 @@
import { describe, expect, it } from "vitest";
import { checkAppleAppI18n } from "../../scripts/apple-app-i18n.ts";
describe("Apple app i18n catalogs", () => {
it("keeps the checked-in Russian and Hindi catalog entries complete", async () => {
await expect(checkAppleAppI18n()).resolves.toBeUndefined();
});
});