mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-15 10:36:09 +00:00
test(android): cover settings model localization
This commit is contained in:
@@ -272,6 +272,9 @@ const UI_BRANCH_LITERAL_RE = /(?:->|return|\?:)\s*"((?:\\.|[^"\\])+)"/gu;
|
||||
const UI_IF_BRANCH_LITERAL_RE = /(?:\bif\s*\([^)]*\)|\belse)\s*\{\s*"((?:\\.|[^"\\])+)"/gu;
|
||||
const UI_MODEL_STRING_NAME_RE =
|
||||
/^(?:contentDescription|errorText|helperText|onClickLabel|statusText)$/u;
|
||||
const UI_MODEL_CLASS_FIELDS = new Map<string, ReadonlySet<string>>([
|
||||
["SettingsToggleRow", new Set(["subtitle", "title"])],
|
||||
]);
|
||||
const KOTLIN_STRING_LITERAL_RE = /"((?:\\.|[^"\\])+)"/gu;
|
||||
|
||||
// These literals are either technical data or immutable source tokens localized at a typed render edge.
|
||||
@@ -592,11 +595,14 @@ function collectTypedModelLiteralFindings(
|
||||
trackTypeArguments: true,
|
||||
});
|
||||
const userFacingParameters = new Map<string, number>();
|
||||
const classFields = UI_MODEL_CLASS_FIELDS.get(className);
|
||||
parameters.forEach((parameter, index) => {
|
||||
const field = parameter.value.match(
|
||||
/\bval\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*String\??(?=\s*(?:=|$))/u,
|
||||
)?.[1];
|
||||
if (field && UI_MODEL_STRING_NAME_RE.test(field)) userFacingParameters.set(field, index);
|
||||
if (field && (UI_MODEL_STRING_NAME_RE.test(field) || classFields?.has(field))) {
|
||||
userFacingParameters.set(field, index);
|
||||
}
|
||||
});
|
||||
if (userFacingParameters.size === 0) continue;
|
||||
const callPattern = new RegExp(`\\b${className}\\s*\\(`, "gu");
|
||||
@@ -729,7 +735,11 @@ function renderStringsXml(
|
||||
manual: readonly ResourceString[],
|
||||
generated: ReadonlyMap<string, { source: string; value: string }>,
|
||||
): string {
|
||||
const lines = ["<resources>"];
|
||||
const lines = [
|
||||
generated.size > 0
|
||||
? '<resources xmlns:tools="http://schemas.android.com/tools">'
|
||||
: "<resources>",
|
||||
];
|
||||
for (const entry of manual) {
|
||||
lines.push(` <string name="${entry.key}"${entry.attrs}>${entry.rawValue}</string>`);
|
||||
}
|
||||
@@ -740,8 +750,10 @@ function renderStringsXml(
|
||||
)) {
|
||||
const formatted = INTERPOLATION_RE.test(entry.source) ? "" : ' formatted="false"';
|
||||
INTERPOLATION_RE.lastIndex = 0;
|
||||
// Translation-memory text intentionally preserves technical tokens and source punctuation,
|
||||
// so Android's English dictionary and typography suggestions do not apply to managed keys.
|
||||
lines.push(
|
||||
` <string name="${key}"${formatted}>"${renderAndroidResourceValue(entry.source, entry.value)}"</string>`,
|
||||
` <string name="${key}"${formatted} tools:ignore="Typos,TypographyDashes,TypographyEllipsis">"${renderAndroidResourceValue(entry.source, entry.value)}"</string>`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ import {
|
||||
describe("Android app i18n resources", () => {
|
||||
it("keeps generated resources, runtime coverage, and every locale aligned", async () => {
|
||||
await expect(checkAndroidAppI18n()).resolves.toBeUndefined();
|
||||
const base = await readFile("apps/android/app/src/main/res/values/strings.xml", "utf8");
|
||||
expect(base).toContain('xmlns:tools="http://schemas.android.com/tools"');
|
||||
expect(base).toMatch(
|
||||
/<string name="native_[a-f0-9]+"[^>]*tools:ignore="Typos,TypographyDashes,TypographyEllipsis">/u,
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves the existing Swedish app name", async () => {
|
||||
@@ -43,6 +48,10 @@ describe("Android app i18n resources", () => {
|
||||
val connected: Boolean,
|
||||
val statusText: String,
|
||||
)
|
||||
data class SettingsToggleRow(
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
)
|
||||
|
||||
Text("Settings")
|
||||
Text(text = nativeStringResource("Connected"))
|
||||
@@ -51,6 +60,8 @@ describe("Android app i18n resources", () => {
|
||||
SettingsMetric("Gateway", gatewayName)
|
||||
ConnectionState(connected = false, statusText = "Connecting to $host")
|
||||
ConnectionState(connected = true, statusText = nativeString("Connected"))
|
||||
SettingsToggleRow("Phone capability", "Share device data")
|
||||
SettingsToggleRow(nativeString("Localized capability"), nativeString("Localized detail"))
|
||||
Text(text = fileName ?: "Attachment")
|
||||
Modifier.clickable(onClickLabel = "Open detail", onClick = {})
|
||||
Text(nativeString("First sentence. ") + "Second sentence.")
|
||||
@@ -73,6 +84,8 @@ describe("Android app i18n resources", () => {
|
||||
expect.objectContaining({ source: "Working" }),
|
||||
expect.objectContaining({ source: "Gateway" }),
|
||||
expect.objectContaining({ source: "Connecting to $host" }),
|
||||
expect.objectContaining({ source: "Phone capability" }),
|
||||
expect.objectContaining({ source: "Share device data" }),
|
||||
expect.objectContaining({ source: "Attachment" }),
|
||||
expect.objectContaining({ source: "Open detail" }),
|
||||
expect.objectContaining({ source: "Second sentence." }),
|
||||
|
||||
Reference in New Issue
Block a user