Skip to content

Commit 07884b7

Browse files
committed
fix: convert lazy "(s)" plural strings to Android quantity resources
Six user-facing strings hardcoded "(s)" for singular/plural nouns, producing awkward "1 daemon(s)" / "1 tool(s)" / "1 section(s)" text. - Converted single-count strings to <plurals> with one/other items: language_pack_extension_metadata_summary, voice_installed, status_bound_summary, daemon_tools_count. - Split multi-count strings (partial_failure_summary, review_message_with_actions) into per-noun <plurals> resources composed in code with buildString. - Migrated call sites from stringRes() to pluralsRes() with the correct quantity parameter. - Fixed adjacent manual .replace() calls to use the stringRes/pluralsRes formatter overload (MCP daemon protocol + tools count).
1 parent c40e32a commit 07884b7

6 files changed

Lines changed: 97 additions & 28 deletions

File tree

app/src/main/kotlin/dev/patrickgold/florisboard/app/ext/ExtensionImportScreen.kt

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import org.florisboard.lib.compose.FlorisSuccessCard
7373
import org.florisboard.lib.compose.FlorisWarningCard
7474
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
7575
import org.florisboard.lib.compose.florisHorizontalScroll
76+
import org.florisboard.lib.compose.pluralsRes
7677
import org.florisboard.lib.compose.stringRes
7778
import org.florisboard.lib.kotlin.resultOk
7879

@@ -324,12 +325,27 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
324325
FlorisSuccessCard(
325326
modifier = Modifier.defaultFlorisOutlinedBox(),
326327
text = stringRes(R.string.ext__import__review_title),
327-
secondaryText = stringRes(
328-
R.string.ext__import__review_message_with_actions,
329-
"new_count" to importSummary.newInstallCount,
330-
"update_count" to importSummary.updateCount,
331-
"skipped_count" to skippedFileCount,
332-
),
328+
secondaryText = buildString {
329+
append(pluralsRes(
330+
R.plurals.ext__import__review_new_files,
331+
importSummary.newInstallCount,
332+
"new_count" to importSummary.newInstallCount,
333+
))
334+
append(", ")
335+
append(pluralsRes(
336+
R.plurals.ext__import__review_updates,
337+
importSummary.updateCount,
338+
"update_count" to importSummary.updateCount,
339+
))
340+
append(", and ")
341+
append(pluralsRes(
342+
R.plurals.ext__import__review_skipped_files,
343+
skippedFileCount,
344+
"skipped_count" to skippedFileCount,
345+
))
346+
append(" ")
347+
append(stringRes(R.string.ext__import__review_message_with_actions_suffix))
348+
},
333349
actionLabel = stringRes(R.string.action__select_files).takeIf {
334350
initUuid == null && ExtensionImportPolicy.canSelectFiles(isPreparingFiles, isImportInProgress)
335351
},

app/src/main/kotlin/dev/patrickgold/florisboard/app/settings/advanced/RestoreScreen.kt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import org.florisboard.lib.compose.FlorisOutlinedButton
7979
import org.florisboard.lib.compose.FlorisProgressCard
8080
import org.florisboard.lib.compose.FlorisWarningCard
8181
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
82+
import org.florisboard.lib.compose.pluralsRes
8283
import org.florisboard.lib.compose.stringRes
8384
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
8485
import org.florisboard.lib.kotlin.io.readJson
@@ -506,11 +507,23 @@ fun RestoreScreen() = FlorisScreen {
506507
FlorisWarningCard(
507508
modifier = Modifier.padding(8.dp),
508509
text = stringRes(R.string.backup_and_restore__restore__partial_failure_title),
509-
secondaryText = stringRes(
510-
R.string.backup_and_restore__restore__partial_failure_summary,
511-
"restored_count" to (summary?.restoredSections ?: 0),
512-
"problem_count" to (summary?.problemSections ?: 0),
513-
),
510+
secondaryText = buildString {
511+
val restored = summary?.restoredSections ?: 0
512+
val problems = summary?.problemSections ?: 0
513+
append(pluralsRes(
514+
R.plurals.backup_and_restore__restore__partial_restored_count,
515+
restored,
516+
"restored_count" to restored,
517+
))
518+
append(". ")
519+
append(pluralsRes(
520+
R.plurals.backup_and_restore__restore__partial_problem_count,
521+
problems,
522+
"problem_count" to problems,
523+
))
524+
append(". ")
525+
append(stringRes(R.string.backup_and_restore__restore__partial_failure_recovery))
526+
},
514527
)
515528
}
516529
RestoreFlowNotice.Success,

app/src/main/kotlin/dev/patrickgold/florisboard/app/settings/localization/LanguagePackManagerScreen.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import org.florisboard.lib.compose.FlorisSuccessCard
6767
import org.florisboard.lib.compose.FlorisTextButton
6868
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
6969
import org.florisboard.lib.compose.rippleClickable
70+
import org.florisboard.lib.compose.pluralsRes
7071
import org.florisboard.lib.compose.stringRes
7172

7273
enum class LanguagePackManagerScreenAction(val id: String) {
@@ -275,8 +276,9 @@ private fun languagePackEntrySummary(entry: LanguagePackCatalogEntry): String {
275276
"total" to entry.componentCount.toString(),
276277
"kind" to kind,
277278
)
278-
LanguagePackRuntimeState.MetadataOnly -> stringRes(
279-
R.string.settings__localization__language_pack_extension_metadata_summary,
279+
LanguagePackRuntimeState.MetadataOnly -> pluralsRes(
280+
R.plurals.settings__localization__language_pack_extension_metadata_summary,
281+
entry.componentCount,
280282
"total" to entry.componentCount.toString(),
281283
)
282284
LanguagePackRuntimeState.DataUnavailable -> stringRes(

app/src/main/kotlin/dev/patrickgold/florisboard/app/settings/mcp/McpSettingsScreen.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import kotlinx.coroutines.launch
5555
import kotlinx.coroutines.withContext
5656
import org.florisboard.lib.compose.FlorisInfoCard
5757
import org.florisboard.lib.compose.FlorisSuccessCard
58+
import org.florisboard.lib.compose.pluralsRes
5859
import org.florisboard.lib.compose.stringRes
5960

6061
/**
@@ -139,8 +140,11 @@ fun McpSettingsScreen() = FlorisScreen {
139140
FlorisSuccessCard(
140141
modifier = Modifier.padding(8.dp),
141142
text = stringRes(R.string.settings__mcp__status_bound_title),
142-
secondaryText = stringRes(R.string.settings__mcp__status_bound_summary)
143-
.replace("{count}", "$activeCount/${activeDaemons.size}"),
143+
secondaryText = pluralsRes(
144+
R.plurals.settings__mcp__status_bound_summary,
145+
activeDaemons.size,
146+
"count" to "$activeCount/${activeDaemons.size}",
147+
),
144148
)
145149
}
146150

@@ -337,14 +341,15 @@ private fun DaemonRow(
337341
icon = if (isEnabled) Icons.Default.PlayCircleOutline else Icons.Default.Block,
338342
title = entry.key.packageName,
339343
summary = buildString {
340-
append(stringRes(R.string.settings__mcp__daemon_protocol).replace(
341-
"{version}",
342-
entry.protocolVersion.toString(),
344+
append(stringRes(
345+
R.string.settings__mcp__daemon_protocol,
346+
"version" to entry.protocolVersion,
343347
))
344348
append(" · ")
345-
append(stringRes(R.string.settings__mcp__daemon_tools_count).replace(
346-
"{count}",
347-
entry.tools.size.toString(),
349+
append(pluralsRes(
350+
R.plurals.settings__mcp__daemon_tools_count,
351+
entry.tools.size,
352+
"count" to entry.tools.size,
348353
))
349354
if (entry.tools.isNotEmpty()) {
350355
append("\n")

app/src/main/kotlin/dev/patrickgold/florisboard/app/settings/privacy/PrivacyPostureScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
5050
import kotlinx.coroutines.launch
5151
import org.florisboard.lib.compose.FlorisErrorCard
5252
import org.florisboard.lib.compose.FlorisSuccessCard
53+
import org.florisboard.lib.compose.pluralsRes
5354
import org.florisboard.lib.compose.stringRes
5455

5556
@Composable
@@ -388,7 +389,7 @@ private fun voiceProviderSummary(
388389
it.state == ExternalVoiceInputProviderState.InstalledNotEnabled
389390
}
390391
return if (installed > 0) {
391-
stringRes(R.string.settings__privacy_posture__voice_installed, "count" to installed)
392+
pluralsRes(R.plurals.settings__privacy_posture__voice_installed, installed, "count" to installed)
392393
} else {
393394
stringRes(R.string.settings__privacy_posture__voice_missing)
394395
}

app/src/main/res/values/strings.xml

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,10 @@
265265
<string name="settings__localization__language_pack_extension_active_summary">{active}/{total} matching current keyboard languages; {kind} pack loads only for active matching subtypes.</string>
266266
<string name="settings__localization__language_pack_extension_standby_summary">0/{total} matching current keyboard languages; {kind} pack stays unloaded until you add or switch to a matching subtype.</string>
267267
<string name="settings__localization__language_pack_extension_unavailable_summary">{active}/{total} matching current keyboard languages, but the {kind} table data is unavailable. Candidates fail closed until you reinstall or replace this pack.</string>
268-
<string name="settings__localization__language_pack_extension_metadata_summary">{total} component(s); generic pack is visible in Settings and ignored by Han shape-based startup loading.</string>
268+
<plurals name="settings__localization__language_pack_extension_metadata_summary">
269+
<item quantity="one">{total} component; generic pack is visible in Settings and ignored by Han shape-based startup loading.</item>
270+
<item quantity="other">{total} components; generic pack is visible in Settings and ignored by Han shape-based startup loading.</item>
271+
</plurals>
269272
<string name="settings__localization__language_pack_component_active_summary">{locale} — active for a current keyboard language</string>
270273
<string name="settings__localization__language_pack_component_standby_summary">{locale} — installed, not active for current keyboard languages</string>
271274
<string name="settings__localization__language_pack_delete_in_progress">Deleting language pack</string>
@@ -1001,7 +1004,10 @@
10011004
<string name="settings__privacy_posture__addons_summary" comment="Preference summary. Placeholders show granted consent surfaces, total surfaces, pinned cert count, and disabled daemon count.">Granted: {granted}/{surfaces}. Signing pins: {pins}. Disabled MCP daemons: {disabled}.</string>
10021005
<string name="settings__privacy_posture__voice_title" comment="Preference title">Voice provider</string>
10031006
<string name="settings__privacy_posture__voice_ready" comment="Preference summary. Placeholder shows provider label.">Ready: {provider}. SwiftFloris only hands off when you tap voice input.</string>
1004-
<string name="settings__privacy_posture__voice_installed" comment="Preference summary. Placeholder shows installed provider count.">{count} external voice provider(s) installed, but setup is incomplete.</string>
1007+
<plurals name="settings__privacy_posture__voice_installed" comment="Preference summary. Placeholder shows installed provider count.">
1008+
<item quantity="one">{count} external voice provider installed, but setup is incomplete.</item>
1009+
<item quantity="other">{count} external voice providers installed, but setup is incomplete.</item>
1010+
</plurals>
10051011
<string name="settings__privacy_posture__voice_missing" comment="Preference summary when no known provider is installed">No supported external voice provider is ready.</string>
10061012
<string name="settings__privacy_posture__export_policy_title" comment="Preference title">Export policy</string>
10071013
<string name="settings__privacy_posture__export_policy_summary" comment="Preference summary">Backups, dictionaries, raw typing traces, and audit logs leave app-private storage only when you explicitly copy, share, or save them. Raw trace files are typed-content artifacts.</string>
@@ -1515,7 +1521,15 @@
15151521
<string name="backup_and_restore__restore__failure_recovery">Restore stopped before all selected data could be imported. Review the archive, select only the needed sections, or retry from a newer backup. Details: {error_message}</string>
15161522
<string name="backup_and_restore__restore__unknown_error">Unknown error</string>
15171523
<string name="backup_and_restore__restore__partial_failure_title">Restore finished with warnings</string>
1518-
<string name="backup_and_restore__restore__partial_failure_summary">Restored {restored_count} section(s). {problem_count} selected section(s) were missing from the archive or could not be restored. Your recovery path is to review the selected archive and retry, or restore from a newer backup.</string>
1524+
<plurals name="backup_and_restore__restore__partial_restored_count">
1525+
<item quantity="one">Restored 1 section</item>
1526+
<item quantity="other">Restored {restored_count} sections</item>
1527+
</plurals>
1528+
<plurals name="backup_and_restore__restore__partial_problem_count">
1529+
<item quantity="one">1 selected section was missing from the archive or could not be restored</item>
1530+
<item quantity="other">{problem_count} selected sections were missing from the archive or could not be restored</item>
1531+
</plurals>
1532+
<string name="backup_and_restore__restore__partial_failure_recovery">Your recovery path is to review the selected archive and retry, or restore from a newer backup.</string>
15191533
<string name="backup_and_restore__restore__partial_failure_toast">Restore finished with warnings.</string>
15201534
<string name="backup_and_restore__restore__empty_title">Select a backup archive</string>
15211535
<string name="backup_and_restore__restore__empty_message">Choose a SwiftFloris backup ZIP to inspect its metadata and select what to restore.</string>
@@ -1801,7 +1815,19 @@
18011815
<string name="ext__import__review_title">Review before importing</string>
18021816
<string name="ext__import__review_message_all_ready">All selected files look ready. Review the details, then import when you are comfortable.</string>
18031817
<string name="ext__import__review_message_with_skips">Compatible files can be imported. Skipped files remain unchanged, so review them before continuing.</string>
1804-
<string name="ext__import__review_message_with_actions">{new_count} new file(s), {update_count} update(s), and {skipped_count} skipped file(s) were found. Review the details, then import when you are comfortable.</string>
1818+
<plurals name="ext__import__review_new_files">
1819+
<item quantity="one">1 new file</item>
1820+
<item quantity="other">{new_count} new files</item>
1821+
</plurals>
1822+
<plurals name="ext__import__review_updates">
1823+
<item quantity="one">1 update</item>
1824+
<item quantity="other">{update_count} updates</item>
1825+
</plurals>
1826+
<plurals name="ext__import__review_skipped_files">
1827+
<item quantity="one">1 skipped file</item>
1828+
<item quantity="other">{skipped_count} skipped files</item>
1829+
</plurals>
1830+
<string name="ext__import__review_message_with_actions_suffix">were found. Review the details, then import when you are comfortable.</string>
18051831
<string name="ext__import__none_ready_title">No importable files</string>
18061832
<string name="ext__import__none_ready_message">Choose a supported SwiftFloris extension archive and try again.</string>
18071833
<string name="ext__import__error_title">Could not prepare import</string>
@@ -2252,9 +2278,15 @@
22522278
<string name="settings__mcp__status_no_daemons" comment="MCP status row title when no daemons are installed">No MCP daemons installed</string>
22532279
<string name="settings__mcp__status_no_daemons_summary" comment="MCP status row summary when no daemons are installed">Install a sibling app that advertises an MCP service; SwiftFloris binds only co-signed or explicitly trusted daemons. The bridge is local-only and never opens a network connection.</string>
22542280
<string name="settings__mcp__status_bound_title" comment="MCP status row title when daemons are bound">MCP bridge active</string>
2255-
<string name="settings__mcp__status_bound_summary" comment="MCP status row summary when daemons are bound; {count} = number">Currently bound to {count} daemon(s). The bridge is local-only and never opens a network connection.</string>
2281+
<plurals name="settings__mcp__status_bound_summary" comment="MCP status row summary when daemons are bound; {count} = active/total ratio">
2282+
<item quantity="one">Currently bound to {count} daemon. The bridge is local-only and never opens a network connection.</item>
2283+
<item quantity="other">Currently bound to {count} daemons. The bridge is local-only and never opens a network connection.</item>
2284+
</plurals>
22562285
<string name="settings__mcp__daemon_protocol" comment="MCP daemon row protocol-version fragment; {version} = integer">Protocol v{version}</string>
2257-
<string name="settings__mcp__daemon_tools_count" comment="MCP daemon row tools-count fragment; {count} = integer">{count} tool(s)</string>
2286+
<plurals name="settings__mcp__daemon_tools_count" comment="MCP daemon row tools-count fragment; {count} = integer">
2287+
<item quantity="one">{count} tool</item>
2288+
<item quantity="other">{count} tools</item>
2289+
</plurals>
22582290
<string name="settings__mcp__rescan" comment="MCP settings action title">Scan MCP daemons</string>
22592291
<string name="settings__mcp__rescan_running" comment="MCP settings action title while scan is running">Scanning MCP daemons…</string>
22602292
<string name="settings__mcp__rescan_summary" comment="MCP settings action summary">Refresh installed daemon trust state. Newly trusted daemons bind the next time the keyboard service starts.</string>

0 commit comments

Comments
 (0)