feat(strings): the app's words for every game (M20) #58
@@ -181,6 +181,13 @@ dependencies {
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
}
|
||||
|
||||
// StringVocabularyTest (PLAN.md M20) reads the string resources from disk. A reworded
|
||||
// string changes no class, so without this input the test task would stay UP-TO-DATE
|
||||
// and the guard would not run again.
|
||||
tasks.withType<Test>().configureEach {
|
||||
inputs.dir("src/main/res").withPathSensitivity(PathSensitivity.RELATIVE)
|
||||
}
|
||||
|
||||
// JaCoCo XML coverage from the JVM unit tests, consumed by SonarQube (§12.1). Generated,
|
||||
// DI (Hilt), and Compose-scaffold classes are excluded so they don't dilute the number;
|
||||
// pure-@Composable UI is excluded on the Sonar side (sonar.coverage.exclusions) because
|
||||
|
||||
@@ -196,8 +196,21 @@ val APP_MENU: List<MenuEntry> = listOf(
|
||||
// Staff operations (§1, M10) — revealed for staff roles; the backend re-checks every call.
|
||||
MenuEntry(Routes.ADMIN_DASHBOARD, R.string.menu_admin_dashboard, MenuAccess.STAFF),
|
||||
MenuEntry(Routes.ADMIN_CONTENT, R.string.menu_admin_content, MenuAccess.STAFF),
|
||||
MenuEntry(Routes.ADMIN_MODERATION, R.string.menu_admin_moderation, MenuAccess.MODERATOR),
|
||||
MenuEntry(Routes.ADMIN_SUPPORT, R.string.menu_admin_support, MenuAccess.MODERATOR),
|
||||
// Moderation and the support queue act on the shard (`/admin/shard/*`, module-uo's
|
||||
// admin mount), so they need the module installed like the player rows do. On a
|
||||
// Rust-only site they once showed a UO form whose every action failed (M20).
|
||||
MenuEntry(
|
||||
Routes.ADMIN_MODERATION,
|
||||
R.string.menu_admin_moderation,
|
||||
MenuAccess.MODERATOR,
|
||||
capability = Capability.SHARD,
|
||||
),
|
||||
MenuEntry(
|
||||
Routes.ADMIN_SUPPORT,
|
||||
R.string.menu_admin_support,
|
||||
MenuAccess.MODERATOR,
|
||||
capability = Capability.SHARD,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -161,7 +161,7 @@ fun describe(row: RustEventDto): FeedLine {
|
||||
*/
|
||||
private fun death(row: RustEventDto, name: String?): FeedLine {
|
||||
val where = listOfNotNull(
|
||||
row.str("weapon")?.let { "with ${prefabName(it)}" },
|
||||
row.str("weapon")?.let { "with ${weaponName(it, row.str("weaponName"))}" },
|
||||
row.num("distance")?.let { "${Math.round(it)}m" },
|
||||
row.str("grid"),
|
||||
if (row.flag("sleeping")) "while sleeping" else null,
|
||||
|
||||
@@ -159,6 +159,23 @@ fun prefabName(name: String?): String {
|
||||
return name.replace(Regex("[_.]+"), " ").trim()
|
||||
}
|
||||
|
||||
/** The endings a held entity's prefab name carries that no player says. */
|
||||
private val WEAPON_ENDING = Regex("[._](entity|weapon|deployed)$")
|
||||
|
||||
/**
|
||||
* The weapon in a kill, the Kotlin half of `module-rust`'s `format.js weapon()`.
|
||||
* The module sends `weaponName` (Module-Rust#60), which is the game's own name for a
|
||||
* gun or the prefab name as words (`server/model/titles/weaponNames.js`), and the
|
||||
* app prints that. A module too old to send it gets the same fallback here: the
|
||||
* `.entity` / `.weapon` / `.deployed` ending is dropped, then [prefabName] turns the
|
||||
* rest into words. So the app never prints "ak47u entity".
|
||||
*/
|
||||
fun weaponName(weapon: String?, weaponName: String?): String {
|
||||
if (weapon.isNullOrBlank()) return ""
|
||||
if (!weaponName.isNullOrBlank()) return weaponName
|
||||
return prefabName(weapon.trim().replace(WEAPON_ENDING, ""))
|
||||
}
|
||||
|
||||
/**
|
||||
* An Economics balance (`docs/modules/rust/PLAN_REDESIGNS.md` §11.2.2): grouped,
|
||||
* at most two places, and a dash for one the server never reported — not "0",
|
||||
|
||||
@@ -4,9 +4,14 @@
|
||||
All user-facing strings are externalized from day one (PLAN.md §2): English is
|
||||
the only bundled locale, but the structure invites community translations.
|
||||
No hardcoded UI strings in Kotlin.
|
||||
|
||||
The strings are split by owner (PLAN.md M20), and Android merges the three files
|
||||
into one R.string: this file is CORE — every screen any deployment can show — and
|
||||
uses core's words only. A game's words live in strings_uo.xml and strings_rust.xml.
|
||||
StringVocabularyTest fails the build when a game's word lands in the wrong file.
|
||||
-->
|
||||
<resources>
|
||||
<!-- Fixed launcher name, baked at build even though in-app branding is per-shard (PLAN.md §13). -->
|
||||
<!-- Fixed launcher name, baked at build even though in-app branding is per-site (PLAN.md §13). -->
|
||||
<string name="app_name">Runic Gateway</string>
|
||||
|
||||
<!-- ── Shared actions / errors ─────────────────────────────────────── -->
|
||||
@@ -15,22 +20,22 @@
|
||||
<string name="error_network">Can\'t reach the site. Check your connection and try again.</string>
|
||||
<string name="error_not_found">This content couldn\'t be found.</string>
|
||||
<string name="error_rate_limited">Too many requests. Please try again in a moment.</string>
|
||||
<string name="error_shard_offline">The shard is offline right now.</string>
|
||||
<string name="error_feature_unavailable">This shard doesn\'t publish this here.</string>
|
||||
<string name="error_shard_offline">The game server is offline right now.</string>
|
||||
<string name="error_feature_unavailable">This site doesn\'t publish this here.</string>
|
||||
<string name="error_server">Something went wrong on the server. Please try again.</string>
|
||||
|
||||
<!-- ── First-run connect (§3) ──────────────────────────────────────── -->
|
||||
<string name="connect_title">Connect to your shard</string>
|
||||
<string name="connect_subtitle">Enter your shard\'s website address to get started.</string>
|
||||
<string name="connect_title">Connect to your community</string>
|
||||
<string name="connect_subtitle">Enter your community\'s website address to get started.</string>
|
||||
<string name="connect_url_label">Website address</string>
|
||||
<string name="connect_url_hint">https://your-shard.example.com</string>
|
||||
<string name="connect_url_hint">https://your-community.example.com</string>
|
||||
<string name="connect_button">Connect</string>
|
||||
<string name="connect_connecting">Connecting…</string>
|
||||
<string name="connect_error_blank">Enter a website address.</string>
|
||||
<string name="connect_error_malformed">That doesn\'t look like a valid web address.</string>
|
||||
<string name="connect_error_scheme">Only http and https addresses are supported.</string>
|
||||
<string name="connect_error_insecure">A secure https address is required.</string>
|
||||
<string name="connect_error_not_runic">That site isn\'t a Runic Gateway shard.</string>
|
||||
<string name="connect_error_not_runic">That site isn\'t a Runic Gateway site.</string>
|
||||
<string name="connect_error_version">This app is out of date for that site (it speaks API %1$s). Update the app and try again.</string>
|
||||
<string name="connect_error_unreachable">Couldn\'t reach that site. Check the address and your connection.</string>
|
||||
<string name="connect_error_server">The site responded with an error (%1$d). Try again shortly.</string>
|
||||
@@ -43,23 +48,12 @@
|
||||
<string name="menu_news">News</string>
|
||||
<string name="menu_events">Events</string>
|
||||
<string name="menu_wiki">Wiki</string>
|
||||
<string name="menu_shard">Shard</string>
|
||||
<string name="menu_rust">Rust servers</string>
|
||||
<string name="menu_rules">Rules</string>
|
||||
<string name="menu_atlas">Atlas</string>
|
||||
<string name="menu_leaderboards">Leaderboards</string>
|
||||
<string name="menu_market">Market</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="menu_contact">Contact</string>
|
||||
<string name="menu_account">My account</string>
|
||||
<string name="menu_my_events">My events</string>
|
||||
<string name="menu_my_characters">My characters</string>
|
||||
<string name="menu_my_vendors">My vendors</string>
|
||||
<string name="menu_my_houses">My houses</string>
|
||||
<string name="menu_admin_dashboard">Dashboard</string>
|
||||
<string name="menu_admin_content">Content</string>
|
||||
<string name="menu_admin_moderation">Moderation</string>
|
||||
<string name="menu_admin_support">Support queue</string>
|
||||
<string name="menu_sign_in">Sign in</string>
|
||||
<string name="menu_sign_out">Sign out</string>
|
||||
<string name="menu_change_server">Change server</string>
|
||||
@@ -110,41 +104,9 @@
|
||||
<string name="admin_content_title_required">A title is required.</string>
|
||||
<string name="admin_content_cat_fields_required">Slug and title are required.</string>
|
||||
|
||||
<!-- Staff moderation (shard write plane) -->
|
||||
<string name="admin_mod_account_action">Account action</string>
|
||||
<string name="admin_mod_account">Account</string>
|
||||
<string name="admin_mod_serial">Serial (0x…)</string>
|
||||
<string name="admin_mod_reason">Reason (ban)</string>
|
||||
<string name="admin_mod_duration">Ban duration (seconds; blank = indefinite)</string>
|
||||
<string name="admin_mod_kick">Kick</string>
|
||||
<string name="admin_mod_ban">Ban</string>
|
||||
<string name="admin_mod_unban">Unban</string>
|
||||
<string name="admin_mod_broadcast_section">Broadcast</string>
|
||||
<string name="admin_mod_broadcast_text">Message to everyone online</string>
|
||||
<string name="admin_mod_broadcast">Broadcast</string>
|
||||
<string name="admin_mod_kicked">Account kicked.</string>
|
||||
<string name="admin_mod_banned">Account banned.</string>
|
||||
<string name="admin_mod_unbanned">Ban cleared.</string>
|
||||
<string name="admin_mod_broadcasted">Message broadcast.</string>
|
||||
<string name="admin_mod_target_required">Enter an account or serial.</string>
|
||||
<string name="admin_mod_text_required">Enter a message to broadcast.</string>
|
||||
<string name="admin_mod_shard_offline">The shard is offline — the action couldn\'t be delivered.</string>
|
||||
|
||||
<!-- Staff support queue -->
|
||||
<string name="admin_support_empty">No open help pages.</string>
|
||||
<string name="admin_support_reply">Reply</string>
|
||||
<string name="admin_support_close">Close</string>
|
||||
<string name="admin_support_send">Send</string>
|
||||
<string name="admin_support_message">Reply message</string>
|
||||
<string name="admin_support_close_after">Close the page after replying</string>
|
||||
<string name="admin_support_responded">Reply sent.</string>
|
||||
<string name="admin_support_closed">Page closed.</string>
|
||||
<string name="admin_support_message_required">Enter a reply message.</string>
|
||||
<string name="admin_support_unknown_page">That page is no longer in the queue.</string>
|
||||
|
||||
<!-- ── Auth: login (§4.1) ──────────────────────────────────────────── -->
|
||||
<string name="login_title">Sign in</string>
|
||||
<string name="login_subtitle">Sign in with your shard account.</string>
|
||||
<string name="login_subtitle">Sign in with your account on this site.</string>
|
||||
<string name="login_username">Username</string>
|
||||
<string name="login_password">Password</string>
|
||||
<string name="login_totp_code">Authentication code</string>
|
||||
@@ -152,7 +114,7 @@
|
||||
<string name="login_button">Sign in</string>
|
||||
<string name="login_register">Create an account</string>
|
||||
<string name="login_forgot">Forgot your password?</string>
|
||||
<!-- Single SSO entry point; the picker lists the shard's providers (native SSO, M9/M10). -->
|
||||
<!-- Single SSO entry point; the picker lists the site's providers (native SSO, M9/M10). -->
|
||||
<string name="login_sso_button">Sign in with SSO</string>
|
||||
<string name="login_sso_pick_title">Choose a sign-in provider</string>
|
||||
<!-- %1$s is the provider name, e.g. "Google" or "Discord". -->
|
||||
@@ -256,77 +218,7 @@
|
||||
<string name="recovery_codes_share">Share</string>
|
||||
<string name="recovery_codes_done">Done</string>
|
||||
|
||||
<!-- ── Player: game-account linking (§6.3) ─────────────────────────── -->
|
||||
<string name="player_link_title">Link your game account</string>
|
||||
<string name="player_link_hint">In game, type [link to get a one-time code, then enter it here to see your characters, vendors and houses.</string>
|
||||
<string name="player_link_code">Link code</string>
|
||||
<string name="player_link_action">Link account</string>
|
||||
<string name="player_link_ok">Account linked.</string>
|
||||
<string name="player_link_bad_code">That code is unknown or has expired. Run [link in game for a new one.</string>
|
||||
<string name="player_link_error">Couldn\'t link that code. Please try again.</string>
|
||||
|
||||
<string name="player_create_title">Create a game account</string>
|
||||
<string name="player_create_account">Account name</string>
|
||||
<string name="player_create_password">Password</string>
|
||||
<string name="player_create_action">Create account</string>
|
||||
<string name="player_create_ok">Game account created and linked.</string>
|
||||
<string name="player_create_taken">That account name is already taken.</string>
|
||||
<string name="player_create_capped">The account limit for your network has been reached.</string>
|
||||
<string name="player_create_unavailable">Game-account signup isn\'t available right now.</string>
|
||||
<string name="player_create_rejected">That account name or password wasn\'t accepted.</string>
|
||||
<string name="player_create_error">Couldn\'t create that account. Please try again.</string>
|
||||
|
||||
<!-- ── Player: characters (§6.3) ───────────────────────────────────── -->
|
||||
<string name="player_characters_empty">Link a game account above to see your characters.</string>
|
||||
<string name="player_roster_loading">Loading…</string>
|
||||
<string name="player_roster_empty">No characters on this account.</string>
|
||||
<string name="player_char_unknown">Unknown</string>
|
||||
<string name="player_char_online">Online</string>
|
||||
<string name="player_char_offline">Offline</string>
|
||||
|
||||
<!-- Character sheet -->
|
||||
<string name="player_char_governor">Governor of %1$s</string>
|
||||
<string name="player_char_guildmaster">Guildmaster, %1$s</string>
|
||||
<string name="player_char_attributes">Attributes</string>
|
||||
<string name="player_char_vitals">Vitals</string>
|
||||
<string name="player_char_str">STR</string>
|
||||
<string name="player_char_dex">DEX</string>
|
||||
<string name="player_char_int">INT</string>
|
||||
<string name="player_char_hits">Hits</string>
|
||||
<string name="player_char_mana">Mana</string>
|
||||
<string name="player_char_stam">Stamina</string>
|
||||
<string name="player_char_resistances">Resistances</string>
|
||||
<string name="player_char_phys">Physical</string>
|
||||
<string name="player_char_fire">Fire</string>
|
||||
<string name="player_char_cold">Cold</string>
|
||||
<string name="player_char_pois">Poison</string>
|
||||
<string name="player_char_energy">Energy</string>
|
||||
<string name="player_char_skills">Skills</string>
|
||||
<string name="player_char_points">Loyalty & Points</string>
|
||||
<!-- A point system's name followed by the character's rank on that board, e.g. "Queens Loyalty · #3". -->
|
||||
<string name="player_char_points_ranked">%1$s · #%2$d</string>
|
||||
<!-- A score against its cap. Only shown for capped systems; an uncapped score shows the number alone. -->
|
||||
<string name="player_char_points_of">%1$d / %2$d</string>
|
||||
<string name="player_char_equipment">Equipment</string>
|
||||
<string name="player_char_item">Item</string>
|
||||
<string name="player_char_item_id">id %1$d</string>
|
||||
<string name="player_char_item_hue">hue %1$d</string>
|
||||
|
||||
<!-- ── Player: vendors & sales (§6.3) ──────────────────────────── -->
|
||||
<string name="player_sales_title">Recent sales</string>
|
||||
<string name="player_sales_empty">No recent sales.</string>
|
||||
<string name="player_gold_amount">%1$s gp</string>
|
||||
<string name="player_vendors_no_accounts">Link a game account to see your vendors.</string>
|
||||
<string name="player_vendors_empty">No vendors on this account.</string>
|
||||
<string name="player_vendor_fallback">Player vendor</string>
|
||||
<string name="player_vendor_hold">Holding %1$s gp</string>
|
||||
<string name="player_vendor_no_listings">No items listed.</string>
|
||||
<string name="player_listing_item">Item %1$d ×%2$d</string>
|
||||
|
||||
<!-- ── Player: houses (§6.3) ───────────────────────────────────────── -->
|
||||
<string name="player_houses_empty">You don\'t have any houses on your linked accounts.</string>
|
||||
<string name="player_house_fallback">A house</string>
|
||||
<string name="player_house_stage">Status: %1$s</string>
|
||||
<!-- ── Roles (the account screen, §5) ──────────────────────────────── -->
|
||||
<string name="role_player">Player</string>
|
||||
<string name="role_moderator">Moderator</string>
|
||||
<string name="role_editor">Editor</string>
|
||||
@@ -365,140 +257,19 @@
|
||||
<string name="contact_validation">Please fill in every field.</string>
|
||||
<string name="contact_error">Couldn\'t send your message. Please try again.</string>
|
||||
|
||||
<!-- ── Shard hub & live feed (§6.2) ─────────────────────────────── -->
|
||||
<string name="shard_live">Live</string>
|
||||
<string name="shard_offline">Offline</string>
|
||||
<string name="shard_status_online">Shard online</string>
|
||||
<string name="shard_status_offline">Shard offline</string>
|
||||
<string name="shard_online_count">%1$d players online</string>
|
||||
<string name="shard_economy_gold">Gold supply: %1$s</string>
|
||||
<string name="shard_section_staff">Staff online</string>
|
||||
<string name="shard_section_activity">Live activity</string>
|
||||
<string name="shard_feed_empty">No recent activity.</string>
|
||||
<string name="shard_nav_champs">Champion spawns</string>
|
||||
<string name="shard_nav_guilds">Guilds</string>
|
||||
<string name="shard_nav_governors">Governors</string>
|
||||
<string name="shard_nav_houses">Falling houses</string>
|
||||
|
||||
<!-- ── Champion spawns (§6.2) ──────────────────────────────────────── -->
|
||||
<string name="champs_empty">No champion spawns are being tracked right now.</string>
|
||||
<string name="champs_fallback_name">Champion spawn</string>
|
||||
<string name="champ_level">Level %1$d</string>
|
||||
<string name="champ_cooldown">Restarting</string>
|
||||
|
||||
<!-- ── Guilds (§6.2) ───────────────────────────────────────────────── -->
|
||||
<string name="guilds_empty">No guilds are being tracked right now.</string>
|
||||
<string name="guilds_fallback_name">Guild</string>
|
||||
<string name="guilds_members">%1$d members</string>
|
||||
<string name="guilds_leader">Led by %1$s</string>
|
||||
<string name="guilds_alliance">Alliance: %1$s</string>
|
||||
|
||||
<!-- ── Rules / ruleset (Protocol 3.0 §5, M11) ──────────────────────── -->
|
||||
<!-- A successful read of a shard that has never published its ruleset — NOT the same
|
||||
as the feature being switched off, which renders as an error state. -->
|
||||
<string name="rules_unpublished">This shard hasn\'t published its ruleset yet.</string>
|
||||
<string name="rules_section_shard">Shard</string>
|
||||
<string name="rules_section_systems">Systems</string>
|
||||
<string name="rules_section_caps">Skill & stat caps</string>
|
||||
<string name="rules_section_accounts">Accounts & housing</string>
|
||||
<string name="rules_section_vendors">Vendors</string>
|
||||
<string name="rules_section_schedule">Saves & restarts</string>
|
||||
<string name="rules_name">Name</string>
|
||||
<string name="rules_expansion">Expansion</string>
|
||||
<string name="rules_connect">Connect</string>
|
||||
<string name="rules_skill_cap">Individual skill cap</string>
|
||||
<string name="rules_total_skill_cap">Total skill cap</string>
|
||||
<string name="rules_stat_cap">Total stat cap</string>
|
||||
<string name="rules_str_cap">Strength cap</string>
|
||||
<string name="rules_dex_cap">Dexterity cap</string>
|
||||
<string name="rules_int_cap">Intelligence cap</string>
|
||||
<string name="rules_char_slots">Character slots</string>
|
||||
<string name="rules_per_ip">Accounts per IP</string>
|
||||
<string name="rules_house_limit">Houses per account</string>
|
||||
<string name="rules_restock_delay">Vendor restock delay</string>
|
||||
<string name="rules_max_sell">Max sell quantity</string>
|
||||
<string name="rules_autosave">Auto-save every</string>
|
||||
<string name="rules_autorestart">Auto-restart at</string>
|
||||
<string name="rules_minutes">%1$d min</string>
|
||||
|
||||
<!-- ── Leaderboards (Protocol 3.0 §7, M11) ─────────────────────────── -->
|
||||
<string name="leaderboards_empty">This shard isn\'t publishing any leaderboards yet.</string>
|
||||
<string name="leaderboards_board_empty">Nobody has scored here yet.</string>
|
||||
<!-- Where a score would sit on the placeholder row of an unscored board. An em
|
||||
dash, not "0" — nobody has scored zero, nobody has scored at all. -->
|
||||
<string name="leaderboards_no_score">—</string>
|
||||
<string name="leaderboards_players">%1$d players</string>
|
||||
<!-- Only shown for capped systems; most systems on a real shard are uncapped. -->
|
||||
<string name="leaderboards_cap">Cap: %1$d</string>
|
||||
<string name="leaderboards_rank_name">#%1$d %2$s</string>
|
||||
<!-- A shard may publish standings without naming who holds them (the board's one
|
||||
admin-configurable field). -->
|
||||
<string name="leaderboards_hidden_name">Someone</string>
|
||||
|
||||
<!-- ── Market (Protocol 3.0 §8, M11) ───────────────────────────────── -->
|
||||
<string name="market_search_label">Search every shop</string>
|
||||
<string name="market_empty">No listings match that search.</string>
|
||||
<string name="market_shop_empty">This shop has nothing for sale.</string>
|
||||
<!-- Required, not decoration: the shard sweeps vendors round-robin, so a price can
|
||||
legitimately be a full cycle old. -->
|
||||
<string name="market_staleness">Prices are refreshed in rotation and may be out of date.</string>
|
||||
<string name="market_price">%1$d gp</string>
|
||||
<string name="market_unnamed_item">Item %1$d</string>
|
||||
<string name="market_unnamed_shop">A shop</string>
|
||||
<string name="market_owner">Kept by %1$s</string>
|
||||
<!-- A gated location is a real answer: the shop exists, the shard just doesn't say
|
||||
where it stands. -->
|
||||
<string name="market_location_hidden">This shard doesn\'t publish shop locations.</string>
|
||||
<string name="market_truncated">Showing %1$d of %2$d — this shop holds more than the shard publishes.</string>
|
||||
|
||||
<!-- ── Spawn atlas (Protocol 3.0 §6, M11) ──────────────────────────── -->
|
||||
<string name="atlas_search_label">Search creatures</string>
|
||||
<string name="atlas_empty">No creatures match that search.</string>
|
||||
<!-- A place can legitimately hold a single spawner, and the aggregate list is full
|
||||
of them — "1 spawners" on every other row is worth a plural for. -->
|
||||
<plurals name="atlas_spawner_count">
|
||||
<item quantity="one">%1$d spawner</item>
|
||||
<item quantity="other">%1$d spawners</item>
|
||||
</plurals>
|
||||
<string name="atlas_total_alive">Up to %1$d alive at once</string>
|
||||
<string name="atlas_facet_count">%1$s (%2$d)</string>
|
||||
<!-- The aggregate: "where is it", as opposed to the raw coordinates below it. -->
|
||||
<string name="atlas_section_places">Where it spawns</string>
|
||||
<string name="atlas_place_max_alive">up to %1$d at once</string>
|
||||
<string name="atlas_section_spawners">Spawn points</string>
|
||||
<string name="atlas_section_also_here">Also spawns here</string>
|
||||
<string name="atlas_spawners_truncated">More spawn points than shown.</string>
|
||||
<string name="atlas_max_count">Up to %1$d</string>
|
||||
<!-- The delay is already in seconds; the server normalizes XmlSpawner's mixed units. -->
|
||||
<string name="atlas_respawn">Respawn %1$s</string>
|
||||
|
||||
<!-- ── Governors (§6.2) ────────────────────────────────────────────── -->
|
||||
<string name="governors_empty">No governors — this shard may not run the City Loyalty system.</string>
|
||||
<string name="governor_current">Governed by %1$s</string>
|
||||
<string name="governor_none">No governor</string>
|
||||
<string name="governor_election">Election: %1$s</string>
|
||||
<string name="governor_history">Term history</string>
|
||||
<string name="governor_history_loading">Loading…</string>
|
||||
<string name="governor_history_empty">No past terms recorded.</string>
|
||||
|
||||
<!-- ── Falling houses / IDOC (§6.2) ────────────────────────────────── -->
|
||||
<string name="houses_empty">No houses are in danger right now.</string>
|
||||
<string name="houses_fallback_name">A house</string>
|
||||
<string name="houses_idoc_badge">IDOC</string>
|
||||
|
||||
<!-- ── Push notifications (§11, M7 Part 2) ─────────────────────────── -->
|
||||
<string name="menu_notifications">Notifications</string>
|
||||
<string name="notifications_title">Notifications</string>
|
||||
<string name="notifications_subtitle">Choose what this shard notifies you about, and how it reaches you. Nothing is sent unless you turn it on.</string>
|
||||
<string name="notifications_subtitle">Choose what this site notifies you about, and how it reaches you. Nothing is sent unless you turn it on.</string>
|
||||
<string name="notifications_section_general">General</string>
|
||||
<string name="notifications_section_personal">Your game account</string>
|
||||
<string name="notifications_requires_link">Link a game account to get this as a push notification.</string>
|
||||
<string name="notifications_empty">This shard offers no notification streams yet.</string>
|
||||
<string name="notifications_unsupported">This shard hasn\'t set up push notifications yet.</string>
|
||||
<string name="notifications_empty">This site offers no notification streams yet.</string>
|
||||
<string name="notifications_unsupported">This site hasn\'t set up push notifications yet.</string>
|
||||
<string name="notifications_saved">Notification settings saved.</string>
|
||||
<string name="notifications_all_off">Notifications turned off.</string>
|
||||
<string name="notifications_save_error">Couldn\'t save your notification settings. Try again.</string>
|
||||
<string name="notifications_relay_error">This shard\'s push relay isn\'t reachable right now.</string>
|
||||
<string name="notifications_relay_error">This site\'s push relay isn\'t reachable right now.</string>
|
||||
|
||||
<!-- The in-app inbox and the per-channel settings (ENGAGEMENT.md phase 8). -->
|
||||
<string name="notifications_mode_off">Off</string>
|
||||
@@ -513,22 +284,15 @@
|
||||
<string name="inbox_offline_cached">Offline — showing what was saved on this device.</string>
|
||||
|
||||
<!-- Notification channels + the ongoing foreground-service notification. -->
|
||||
<string name="push_channel_messages">Shard notifications</string>
|
||||
<string name="push_channel_messages_desc">Alerts you opted into from this shard.</string>
|
||||
<string name="push_channel_messages">Notifications from your community</string>
|
||||
<string name="push_channel_messages_desc">Alerts you opted into from this site.</string>
|
||||
<string name="push_channel_service">Background connection</string>
|
||||
<string name="push_channel_service_desc">Keeps the connection open to deliver notifications.</string>
|
||||
<string name="push_service_title">Notifications active</string>
|
||||
<string name="push_service_text">Listening for shard notifications.</string>
|
||||
<string name="push_service_text">Listening for notifications from your community.</string>
|
||||
|
||||
<!-- Per-stream notification titles (content-free tickle → generic title, §11). -->
|
||||
<string name="push_stream_news_post">New post</string>
|
||||
<string name="push_stream_server_status">Shard status changed</string>
|
||||
<string name="push_stream_idoc_warning">A house is falling (IDOC)</string>
|
||||
<string name="push_stream_champ_start">Champion spawn started</string>
|
||||
<string name="push_stream_governor_election">New governor elected</string>
|
||||
<string name="push_stream_vendor_sale">Your vendor made a sale</string>
|
||||
<string name="push_stream_house_idoc">Your house entered IDOC</string>
|
||||
<string name="push_stream_account_login">Login to your account</string>
|
||||
<string name="push_stream_generic">New notification</string>
|
||||
|
||||
<!-- ── Events (§9 M13, EVENTS.md §I) ─────────────────────────── -->
|
||||
@@ -571,150 +335,4 @@
|
||||
<string name="events_score">Score %1$s</string>
|
||||
<string name="events_show_more">Show more</string>
|
||||
<string name="events_loading">Loading…</string>
|
||||
|
||||
<!-- Rust module (M14, docs/modules/rust/PLAN.md §18) -->
|
||||
<string name="rust_servers_empty">No Rust servers are configured on this site yet.</string>
|
||||
<string name="rust_offline">Offline</string>
|
||||
<string name="rust_online_count">%1$d / %2$d online</string>
|
||||
<string name="rust_never_reported">has never reported</string>
|
||||
<string name="rust_last_reported">last reported %1$s</string>
|
||||
<string name="rust_last_reported_stale">last reported %1$s — out of date, so it is shown as offline</string>
|
||||
<string name="rust_world_size">size %1$d</string>
|
||||
<string name="rust_world_seed">seed %1$d</string>
|
||||
<string name="rust_wiped_on">wiped %1$s</string>
|
||||
<string name="rust_next_wipe">Next wipe %1$s</string>
|
||||
<string name="rust_next_wipe_rescheduled">Next wipe %1$s (rescheduled)</string>
|
||||
<string name="rust_refresh_failed">Could not refresh just now. This is the last thing the site heard.</string>
|
||||
<string name="rust_no_such_server">No such server</string>
|
||||
<string name="rust_no_such_server_detail">This address does not name a server this site follows.</string>
|
||||
<string name="rust_back_to_servers">Back to the server list</string>
|
||||
<string name="rust_tab_feed">Feed</string>
|
||||
<string name="rust_tab_leaderboard">Leaderboard</string>
|
||||
<string name="rust_tab_online">Online</string>
|
||||
<string name="rust_tab_wipes">Wipes</string>
|
||||
<string name="rust_tab_map">Map</string>
|
||||
<!-- The Map tab (Rust phase 15, D121–D125). The wording follows the web's Map
|
||||
tab, so a reader moving between the two reads the same sentences. -->
|
||||
<string name="rust_map_none">No map yet. This server has not told the site which map it is on. The map will appear once the server is up.</string>
|
||||
<string name="rust_map_no_picture">This server has no picture of its map, so the layers are drawn on a plain background.</string>
|
||||
<string name="rust_map_picture_failed">The picture of the map could not be loaded, so the layers are drawn on a plain background.</string>
|
||||
<string name="rust_map_description">Map of the server</string>
|
||||
<string name="rust_map_status_nothing_live">No moving layers are shown to you on this server.</string>
|
||||
<string name="rust_map_status_asking">Asking the server where things are…</string>
|
||||
<string name="rust_map_status_not_now">The server did not say where things are just now. The site asks again every ten seconds.</string>
|
||||
<string name="rust_map_status_offline">The server is offline, so nothing is moving on its map.</string>
|
||||
<string name="rust_map_status_failed">Positions could not be loaded.</string>
|
||||
<string name="rust_map_status_failed_kept">Could not refresh just now. These are the last positions the site heard.</string>
|
||||
<string name="rust_map_status_as_of">Positions as of %1$s. They refresh every ten seconds while this tab is open.</string>
|
||||
<string name="rust_map_layer_grid">Grid</string>
|
||||
<string name="rust_map_layer_world">Monuments & world events</string>
|
||||
<string name="rust_map_layer_events">Site events</string>
|
||||
<string name="rust_map_layer_players">Players</string>
|
||||
<string name="rust_map_layer_bases">Bases</string>
|
||||
<string name="rust_map_layer_mates">You and your clan</string>
|
||||
<string name="rust_map_hidden_signin">Sign in to see this layer.</string>
|
||||
<string name="rust_map_hidden_public">Shown to everyone.</string>
|
||||
<string name="rust_map_hidden_staff">Shown to staff only.</string>
|
||||
<string name="rust_map_capped">%1$s Limited by who may see who is online.</string>
|
||||
<string name="rust_map_players_truncated">Not every sleeper is shown.</string>
|
||||
<string name="rust_map_bases_truncated">Not every base is shown.</string>
|
||||
<string name="rust_map_mates_note">Your own position, and clan mates who are online.</string>
|
||||
<string name="rust_map_mates_link">Link your Steam account to see yourself and your clan here.</string>
|
||||
<string name="rust_map_cargo">Cargo ship</string>
|
||||
<string name="rust_map_heli">Patrol helicopter</string>
|
||||
<string name="rust_map_chinook">Chinook</string>
|
||||
<string name="rust_map_bradley">Bradley APC</string>
|
||||
<string name="rust_map_supply">Supply drop</string>
|
||||
<string name="rust_map_crate">Locked crate</string>
|
||||
<string name="rust_map_crate_hack">%1$s left on the hack</string>
|
||||
<string name="rust_map_crate_hacked">Hacked</string>
|
||||
<string name="rust_map_event_zone">Event zone</string>
|
||||
<string name="rust_map_event_crate">Event crate</string>
|
||||
<string name="rust_map_event_npc">Event NPC</string>
|
||||
<string name="rust_map_site_event">Site event</string>
|
||||
<string name="rust_map_site_event_named">Site event · %1$s</string>
|
||||
<string name="rust_map_boss">%1$s (boss)</string>
|
||||
<string name="rust_map_boss_unnamed">Boss</string>
|
||||
<string name="rust_map_boss_add">%1$s (a boss\'s add)</string>
|
||||
<string name="rust_map_open_event">Open event</string>
|
||||
<string name="rust_map_tc">Tool cupboard</string>
|
||||
<string name="rust_map_vending">Vending machine</string>
|
||||
<string name="rust_map_player_sleeping">Sleeping</string>
|
||||
<string name="rust_map_player_offline">Asleep, offline</string>
|
||||
<string name="rust_map_you">You</string>
|
||||
<string name="rust_map_clan_mate">Clan mate</string>
|
||||
<string name="rust_map_grid">Grid %1$s</string>
|
||||
<string name="rust_map_close">Close</string>
|
||||
<string name="rust_all_time">All time</string>
|
||||
<string name="rust_wipe_current">%1$s (this wipe)</string>
|
||||
<string name="rust_wipe_this_one">Current</string>
|
||||
<string name="rust_wipe_selected">Showing</string>
|
||||
<string name="rust_feed_empty">Nothing has happened on this server yet — or not during the wipe you are looking at.</string>
|
||||
<string name="rust_leaderboard_empty">Nobody has scored here yet.</string>
|
||||
<string name="rust_wipes_empty">This server has not reported a wipe yet.</string>
|
||||
<!-- Nothing names who is online by default (org lead, 2026-09-22). Below the
|
||||
operator's audience the server sends a count and no names. -->
|
||||
<plurals name="rust_online_hidden_count">
|
||||
<item quantity="one">%1$d player online</item>
|
||||
<item quantity="other">%1$d players online</item>
|
||||
</plurals>
|
||||
<string name="rust_online_hidden_staff">Only this site’s staff can see who they are.</string>
|
||||
<string name="rust_online_hidden_signin">Sign in to see who they are.</string>
|
||||
<string name="rust_online_hidden_public">This site is not showing who they are right now.</string>
|
||||
<string name="rust_feed_presence_hidden_staff">Joins, deaths and chat are not shown. Only this site’s staff can see what players did.</string>
|
||||
<string name="rust_feed_presence_hidden_signin">Joins, deaths and chat are not shown. Sign in to see what players did.</string>
|
||||
<string name="rust_nobody_on">The server is up and the island is empty. Somebody has to be first.</string>
|
||||
<string name="rust_presence_offline">Presence is the one thing on this page that cannot be answered from the record — it is who is connected now, and nothing is.</string>
|
||||
<string name="rust_presence_live">On the server right now.</string>
|
||||
<string name="rust_presence_last_known">The last board this server sent. It is offline, so this is who was on then — not who is on now.</string>
|
||||
<string name="rust_sleeping">Sleeping</string>
|
||||
<string name="rust_col_player">Player</string>
|
||||
<string name="rust_col_kills">Kills</string>
|
||||
<string name="rust_col_deaths">Deaths</string>
|
||||
<string name="rust_col_npc_kills">NPC</string>
|
||||
<string name="rust_col_structures">Built</string>
|
||||
<string name="rust_col_played">Played</string>
|
||||
<string name="rust_online_badge">%1$d players online</string>
|
||||
|
||||
<!-- The player's own Rust account (M15 — module-rust phase 8) -->
|
||||
<string name="menu_rust_account">My Rust account</string>
|
||||
<string name="rust_link_title">Link your Steam account</string>
|
||||
<string name="rust_link_hint">Join any of our Rust servers and type /link in chat. The server replies with a six-character code only you can see, good for five minutes. Enter it here — it works once.</string>
|
||||
<string name="rust_link_code">Link code</string>
|
||||
<string name="rust_link_action">Link account</string>
|
||||
<string name="rust_link_ok">Linked. Your play on our servers now appears under your name here.</string>
|
||||
<string name="rust_link_already">That account was already linked to you.</string>
|
||||
<string name="rust_link_bad_code">That code is unknown or has expired. Type /link in game for a new one.</string>
|
||||
<string name="rust_link_taken">That Steam account is linked to another website account. Run /unlink in game to release it.</string>
|
||||
<string name="rust_link_capped">Too many attempts just now. Try again in a few minutes.</string>
|
||||
<string name="rust_link_unreachable">A server could not be reached, so that code could not be checked. Your code is still good — try again in a minute.</string>
|
||||
<string name="rust_link_error">Could not link that code.</string>
|
||||
<string name="rust_unlink_action">Unlink</string>
|
||||
<string name="rust_unlink_error">Could not unlink that account.</string>
|
||||
<string name="rust_account_linked_when">linked %1$s</string>
|
||||
<string name="rust_account_none">No Steam account is linked to this profile yet.</string>
|
||||
<string name="rust_account_fleet_note">A link covers every server this community runs — a Steam account is one person wherever they play. You can also type /unlink in game.</string>
|
||||
<string name="rust_held_title">What you can do in game</string>
|
||||
<string name="rust_held_empty">Nothing yet. Ranks and rewards this site hands out show up here, and reach you in game on the servers they cover.</string>
|
||||
<string name="rust_held_unlinked">None of this reaches the game yet — link a Steam account above and the site pushes it across on its next sync.</string>
|
||||
<string name="rust_held_waiting_note">A server marked waiting has not confirmed it yet. One that is offline catches up when it comes back.</string>
|
||||
<string name="rust_held_no_servers">No servers are configured yet.</string>
|
||||
<string name="rust_coins_title">Coins</string>
|
||||
<string name="rust_coins_unlinked">Link a Steam account to see your balance.</string>
|
||||
<string name="rust_coins_missing">Economics is not installed on this server.</string>
|
||||
<string name="rust_coins_balance">%1$s · %2$s coins</string>
|
||||
<string name="rust_coins_last_known">Last known — the server did not answer just now.</string>
|
||||
<string name="rust_backpack_title">Backpack</string>
|
||||
<string name="rust_backpack_unlinked">Link a Steam account to see your backpack.</string>
|
||||
<string name="rust_backpack_open">Open backpack</string>
|
||||
<string name="rust_backpack_read_again">Read again</string>
|
||||
<string name="rust_backpack_close">Close</string>
|
||||
<string name="rust_backpack_missing">Backpacks is not installed on this server.</string>
|
||||
<string name="rust_backpack_unavailable">This site cannot show backpacks yet.</string>
|
||||
<string name="rust_backpack_none">No backpack here yet — it has never been opened.</string>
|
||||
<string name="rust_backpack_summary">%1$d stacks in %2$d slots</string>
|
||||
<string name="rust_backpack_overflow">%1$d past your size — they move to your inventory, or drop at your feet if it is full, the next time you open it.</string>
|
||||
<string name="rust_backpack_page">Page %1$d</string>
|
||||
<string name="rust_reach_live">%1$s · has it</string>
|
||||
<string name="rust_reach_waiting">%1$s · waiting</string>
|
||||
</resources>
|
||||
|
||||
157
app/src/main/res/values/strings_rust.xml
Normal file
157
app/src/main/res/values/strings_rust.xml
Normal file
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
|
||||
<!--
|
||||
The Rust module's screens (PLAN.md M20): the server list and server page, the map,
|
||||
and the player's own Rust account. Every row that reaches these is gated on the rust
|
||||
capability, so Rust's words are right here. Core strings live in strings.xml.
|
||||
-->
|
||||
<resources>
|
||||
<!-- ── Navigation menu (§5) ────────────────────────────────────────── -->
|
||||
<string name="menu_rust">Rust servers</string>
|
||||
|
||||
<!-- Rust module (M14, docs/modules/rust/PLAN.md §18) -->
|
||||
<string name="rust_servers_empty">No Rust servers are configured on this site yet.</string>
|
||||
<string name="rust_offline">Offline</string>
|
||||
<string name="rust_online_count">%1$d / %2$d online</string>
|
||||
<string name="rust_never_reported">has never reported</string>
|
||||
<string name="rust_last_reported">last reported %1$s</string>
|
||||
<string name="rust_last_reported_stale">last reported %1$s — out of date, so it is shown as offline</string>
|
||||
<string name="rust_world_size">size %1$d</string>
|
||||
<string name="rust_world_seed">seed %1$d</string>
|
||||
<string name="rust_wiped_on">wiped %1$s</string>
|
||||
<string name="rust_next_wipe">Next wipe %1$s</string>
|
||||
<string name="rust_next_wipe_rescheduled">Next wipe %1$s (rescheduled)</string>
|
||||
<string name="rust_refresh_failed">Could not refresh just now. This is the last thing the site heard.</string>
|
||||
<string name="rust_no_such_server">No such server</string>
|
||||
<string name="rust_no_such_server_detail">This address does not name a server this site follows.</string>
|
||||
<string name="rust_back_to_servers">Back to the server list</string>
|
||||
<string name="rust_tab_feed">Feed</string>
|
||||
<string name="rust_tab_leaderboard">Leaderboard</string>
|
||||
<string name="rust_tab_online">Online</string>
|
||||
<string name="rust_tab_wipes">Wipes</string>
|
||||
<string name="rust_tab_map">Map</string>
|
||||
<!-- The Map tab (Rust phase 15, D121–D125). The wording follows the web's Map
|
||||
tab, so a reader moving between the two reads the same sentences. -->
|
||||
<string name="rust_map_none">No map yet. This server has not told the site which map it is on. The map will appear once the server is up.</string>
|
||||
<string name="rust_map_no_picture">This server has no picture of its map, so the layers are drawn on a plain background.</string>
|
||||
<string name="rust_map_picture_failed">The picture of the map could not be loaded, so the layers are drawn on a plain background.</string>
|
||||
<string name="rust_map_description">Map of the server</string>
|
||||
<string name="rust_map_status_nothing_live">No moving layers are shown to you on this server.</string>
|
||||
<string name="rust_map_status_asking">Asking the server where things are…</string>
|
||||
<string name="rust_map_status_not_now">The server did not say where things are just now. The site asks again every ten seconds.</string>
|
||||
<string name="rust_map_status_offline">The server is offline, so nothing is moving on its map.</string>
|
||||
<string name="rust_map_status_failed">Positions could not be loaded.</string>
|
||||
<string name="rust_map_status_failed_kept">Could not refresh just now. These are the last positions the site heard.</string>
|
||||
<string name="rust_map_status_as_of">Positions as of %1$s. They refresh every ten seconds while this tab is open.</string>
|
||||
<string name="rust_map_layer_grid">Grid</string>
|
||||
<string name="rust_map_layer_world">Monuments & world events</string>
|
||||
<string name="rust_map_layer_events">Site events</string>
|
||||
<string name="rust_map_layer_players">Players</string>
|
||||
<string name="rust_map_layer_bases">Bases</string>
|
||||
<string name="rust_map_layer_mates">You and your clan</string>
|
||||
<string name="rust_map_hidden_signin">Sign in to see this layer.</string>
|
||||
<string name="rust_map_hidden_public">Shown to everyone.</string>
|
||||
<string name="rust_map_hidden_staff">Shown to staff only.</string>
|
||||
<string name="rust_map_capped">%1$s Limited by who may see who is online.</string>
|
||||
<string name="rust_map_players_truncated">Not every sleeper is shown.</string>
|
||||
<string name="rust_map_bases_truncated">Not every base is shown.</string>
|
||||
<string name="rust_map_mates_note">Your own position, and clan mates who are online.</string>
|
||||
<string name="rust_map_mates_link">Link your Steam account to see yourself and your clan here.</string>
|
||||
<string name="rust_map_cargo">Cargo ship</string>
|
||||
<string name="rust_map_heli">Patrol helicopter</string>
|
||||
<string name="rust_map_chinook">Chinook</string>
|
||||
<string name="rust_map_bradley">Bradley APC</string>
|
||||
<string name="rust_map_supply">Supply drop</string>
|
||||
<string name="rust_map_crate">Locked crate</string>
|
||||
<string name="rust_map_crate_hack">%1$s left on the hack</string>
|
||||
<string name="rust_map_crate_hacked">Hacked</string>
|
||||
<string name="rust_map_event_zone">Event zone</string>
|
||||
<string name="rust_map_event_crate">Event crate</string>
|
||||
<string name="rust_map_event_npc">Event NPC</string>
|
||||
<string name="rust_map_site_event">Site event</string>
|
||||
<string name="rust_map_site_event_named">Site event · %1$s</string>
|
||||
<string name="rust_map_boss">%1$s (boss)</string>
|
||||
<string name="rust_map_boss_unnamed">Boss</string>
|
||||
<string name="rust_map_boss_add">%1$s (a boss\'s add)</string>
|
||||
<string name="rust_map_open_event">Open event</string>
|
||||
<string name="rust_map_tc">Tool cupboard</string>
|
||||
<string name="rust_map_vending">Vending machine</string>
|
||||
<string name="rust_map_player_sleeping">Sleeping</string>
|
||||
<string name="rust_map_player_offline">Asleep, offline</string>
|
||||
<string name="rust_map_you">You</string>
|
||||
<string name="rust_map_clan_mate">Clan mate</string>
|
||||
<string name="rust_map_grid">Grid %1$s</string>
|
||||
<string name="rust_map_close">Close</string>
|
||||
<string name="rust_all_time">All time</string>
|
||||
<string name="rust_wipe_current">%1$s (this wipe)</string>
|
||||
<string name="rust_wipe_this_one">Current</string>
|
||||
<string name="rust_wipe_selected">Showing</string>
|
||||
<string name="rust_feed_empty">Nothing has happened on this server yet — or not during the wipe you are looking at.</string>
|
||||
<string name="rust_leaderboard_empty">Nobody has scored here yet.</string>
|
||||
<string name="rust_wipes_empty">This server has not reported a wipe yet.</string>
|
||||
<!-- Nothing names who is online by default (org lead, 2026-09-22). Below the
|
||||
operator's audience the server sends a count and no names. -->
|
||||
<plurals name="rust_online_hidden_count">
|
||||
<item quantity="one">%1$d player online</item>
|
||||
<item quantity="other">%1$d players online</item>
|
||||
</plurals>
|
||||
<string name="rust_online_hidden_staff">Only this site’s staff can see who they are.</string>
|
||||
<string name="rust_online_hidden_signin">Sign in to see who they are.</string>
|
||||
<string name="rust_online_hidden_public">This site is not showing who they are right now.</string>
|
||||
<string name="rust_feed_presence_hidden_staff">Joins, deaths and chat are not shown. Only this site’s staff can see what players did.</string>
|
||||
<string name="rust_feed_presence_hidden_signin">Joins, deaths and chat are not shown. Sign in to see what players did.</string>
|
||||
<string name="rust_nobody_on">The server is up and the island is empty. Somebody has to be first.</string>
|
||||
<string name="rust_presence_offline">Presence is the one thing on this page that cannot be answered from the record — it is who is connected now, and nothing is.</string>
|
||||
<string name="rust_presence_live">On the server right now.</string>
|
||||
<string name="rust_presence_last_known">The last board this server sent. It is offline, so this is who was on then — not who is on now.</string>
|
||||
<string name="rust_sleeping">Sleeping</string>
|
||||
<string name="rust_col_player">Player</string>
|
||||
<string name="rust_col_kills">Kills</string>
|
||||
<string name="rust_col_deaths">Deaths</string>
|
||||
<string name="rust_col_npc_kills">NPC</string>
|
||||
<string name="rust_col_structures">Built</string>
|
||||
<string name="rust_col_played">Played</string>
|
||||
<string name="rust_online_badge">%1$d players online</string>
|
||||
|
||||
<!-- The player's own Rust account (M15 — module-rust phase 8) -->
|
||||
<string name="menu_rust_account">My Rust account</string>
|
||||
<string name="rust_link_title">Link your Steam account</string>
|
||||
<string name="rust_link_hint">Join any of our Rust servers and type /link in chat. The server replies with a six-character code only you can see, good for five minutes. Enter it here — it works once.</string>
|
||||
<string name="rust_link_code">Link code</string>
|
||||
<string name="rust_link_action">Link account</string>
|
||||
<string name="rust_link_ok">Linked. Your play on our servers now appears under your name here.</string>
|
||||
<string name="rust_link_already">That account was already linked to you.</string>
|
||||
<string name="rust_link_bad_code">That code is unknown or has expired. Type /link in game for a new one.</string>
|
||||
<string name="rust_link_taken">That Steam account is linked to another website account. Run /unlink in game to release it.</string>
|
||||
<string name="rust_link_capped">Too many attempts just now. Try again in a few minutes.</string>
|
||||
<string name="rust_link_unreachable">A server could not be reached, so that code could not be checked. Your code is still good — try again in a minute.</string>
|
||||
<string name="rust_link_error">Could not link that code.</string>
|
||||
<string name="rust_unlink_action">Unlink</string>
|
||||
<string name="rust_unlink_error">Could not unlink that account.</string>
|
||||
<string name="rust_account_linked_when">linked %1$s</string>
|
||||
<string name="rust_account_none">No Steam account is linked to this profile yet.</string>
|
||||
<string name="rust_account_fleet_note">A link covers every server this community runs — a Steam account is one person wherever they play. You can also type /unlink in game.</string>
|
||||
<string name="rust_held_title">What you can do in game</string>
|
||||
<string name="rust_held_empty">Nothing yet. Ranks and rewards this site hands out show up here, and reach you in game on the servers they cover.</string>
|
||||
<string name="rust_held_unlinked">None of this reaches the game yet — link a Steam account above and the site pushes it across on its next sync.</string>
|
||||
<string name="rust_held_waiting_note">A server marked waiting has not confirmed it yet. One that is offline catches up when it comes back.</string>
|
||||
<string name="rust_held_no_servers">No servers are configured yet.</string>
|
||||
<string name="rust_coins_title">Coins</string>
|
||||
<string name="rust_coins_unlinked">Link a Steam account to see your balance.</string>
|
||||
<string name="rust_coins_missing">Economics is not installed on this server.</string>
|
||||
<string name="rust_coins_balance">%1$s · %2$s coins</string>
|
||||
<string name="rust_coins_last_known">Last known — the server did not answer just now.</string>
|
||||
<string name="rust_backpack_title">Backpack</string>
|
||||
<string name="rust_backpack_unlinked">Link a Steam account to see your backpack.</string>
|
||||
<string name="rust_backpack_open">Open backpack</string>
|
||||
<string name="rust_backpack_read_again">Read again</string>
|
||||
<string name="rust_backpack_close">Close</string>
|
||||
<string name="rust_backpack_missing">Backpacks is not installed on this server.</string>
|
||||
<string name="rust_backpack_unavailable">This site cannot show backpacks yet.</string>
|
||||
<string name="rust_backpack_none">No backpack here yet — it has never been opened.</string>
|
||||
<string name="rust_backpack_summary">%1$d stacks in %2$d slots</string>
|
||||
<string name="rust_backpack_overflow">%1$d past your size — they move to your inventory, or drop at your feet if it is full, the next time you open it.</string>
|
||||
<string name="rust_backpack_page">Page %1$d</string>
|
||||
<string name="rust_reach_live">%1$s · has it</string>
|
||||
<string name="rust_reach_waiting">%1$s · waiting</string>
|
||||
</resources>
|
||||
256
app/src/main/res/values/strings_uo.xml
Normal file
256
app/src/main/res/values/strings_uo.xml
Normal file
@@ -0,0 +1,256 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
|
||||
<!--
|
||||
The Ultima Online module's screens (PLAN.md M20): the shard hub and its pages, the
|
||||
player's characters / vendors / houses, staff moderation and the support queue, and
|
||||
the module's push streams. Every row that reaches these is gated on the shard
|
||||
capability, so UO's words are right here. Core strings live in strings.xml.
|
||||
-->
|
||||
<resources>
|
||||
<!-- ── Navigation menu (§5) ────────────────────────────────────────── -->
|
||||
<string name="menu_shard">Shard</string>
|
||||
<string name="menu_rules">Rules</string>
|
||||
<string name="menu_atlas">Atlas</string>
|
||||
<string name="menu_leaderboards">Leaderboards</string>
|
||||
<string name="menu_market">Market</string>
|
||||
<string name="menu_my_characters">My characters</string>
|
||||
<string name="menu_my_vendors">My vendors</string>
|
||||
<string name="menu_my_houses">My houses</string>
|
||||
<string name="menu_admin_moderation">Moderation</string>
|
||||
<string name="menu_admin_support">Support queue</string>
|
||||
|
||||
<!-- ── Staff operations (§1, M10) ──────────────────────────────────── -->
|
||||
<!-- Staff moderation (shard write plane) -->
|
||||
<string name="admin_mod_account_action">Account action</string>
|
||||
<string name="admin_mod_account">Account</string>
|
||||
<string name="admin_mod_serial">Serial (0x…)</string>
|
||||
<string name="admin_mod_reason">Reason (ban)</string>
|
||||
<string name="admin_mod_duration">Ban duration (seconds; blank = indefinite)</string>
|
||||
<string name="admin_mod_kick">Kick</string>
|
||||
<string name="admin_mod_ban">Ban</string>
|
||||
<string name="admin_mod_unban">Unban</string>
|
||||
<string name="admin_mod_broadcast_section">Broadcast</string>
|
||||
<string name="admin_mod_broadcast_text">Message to everyone online</string>
|
||||
<string name="admin_mod_broadcast">Broadcast</string>
|
||||
<string name="admin_mod_kicked">Account kicked.</string>
|
||||
<string name="admin_mod_banned">Account banned.</string>
|
||||
<string name="admin_mod_unbanned">Ban cleared.</string>
|
||||
<string name="admin_mod_broadcasted">Message broadcast.</string>
|
||||
<string name="admin_mod_target_required">Enter an account or serial.</string>
|
||||
<string name="admin_mod_text_required">Enter a message to broadcast.</string>
|
||||
<string name="admin_mod_shard_offline">The shard is offline — the action couldn\'t be delivered.</string>
|
||||
|
||||
<!-- Staff support queue -->
|
||||
<string name="admin_support_empty">No open help pages.</string>
|
||||
<string name="admin_support_reply">Reply</string>
|
||||
<string name="admin_support_close">Close</string>
|
||||
<string name="admin_support_send">Send</string>
|
||||
<string name="admin_support_message">Reply message</string>
|
||||
<string name="admin_support_close_after">Close the page after replying</string>
|
||||
<string name="admin_support_responded">Reply sent.</string>
|
||||
<string name="admin_support_closed">Page closed.</string>
|
||||
<string name="admin_support_message_required">Enter a reply message.</string>
|
||||
<string name="admin_support_unknown_page">That page is no longer in the queue.</string>
|
||||
|
||||
<!-- ── Player: game-account linking (§6.3) ─────────────────────────── -->
|
||||
<string name="player_link_title">Link your game account</string>
|
||||
<string name="player_link_hint">In game, type [link to get a one-time code, then enter it here to see your characters, vendors and houses.</string>
|
||||
<string name="player_link_code">Link code</string>
|
||||
<string name="player_link_action">Link account</string>
|
||||
<string name="player_link_ok">Account linked.</string>
|
||||
<string name="player_link_bad_code">That code is unknown or has expired. Run [link in game for a new one.</string>
|
||||
<string name="player_link_error">Couldn\'t link that code. Please try again.</string>
|
||||
|
||||
<string name="player_create_title">Create a game account</string>
|
||||
<string name="player_create_account">Account name</string>
|
||||
<string name="player_create_password">Password</string>
|
||||
<string name="player_create_action">Create account</string>
|
||||
<string name="player_create_ok">Game account created and linked.</string>
|
||||
<string name="player_create_taken">That account name is already taken.</string>
|
||||
<string name="player_create_capped">The account limit for your network has been reached.</string>
|
||||
<string name="player_create_unavailable">Game-account signup isn\'t available right now.</string>
|
||||
<string name="player_create_rejected">That account name or password wasn\'t accepted.</string>
|
||||
<string name="player_create_error">Couldn\'t create that account. Please try again.</string>
|
||||
|
||||
<!-- ── Player: characters (§6.3) ───────────────────────────────────── -->
|
||||
<string name="player_characters_empty">Link a game account above to see your characters.</string>
|
||||
<string name="player_roster_loading">Loading…</string>
|
||||
<string name="player_roster_empty">No characters on this account.</string>
|
||||
<string name="player_char_unknown">Unknown</string>
|
||||
<string name="player_char_online">Online</string>
|
||||
<string name="player_char_offline">Offline</string>
|
||||
|
||||
<!-- Character sheet -->
|
||||
<string name="player_char_governor">Governor of %1$s</string>
|
||||
<string name="player_char_guildmaster">Guildmaster, %1$s</string>
|
||||
<string name="player_char_attributes">Attributes</string>
|
||||
<string name="player_char_vitals">Vitals</string>
|
||||
<string name="player_char_str">STR</string>
|
||||
<string name="player_char_dex">DEX</string>
|
||||
<string name="player_char_int">INT</string>
|
||||
<string name="player_char_hits">Hits</string>
|
||||
<string name="player_char_mana">Mana</string>
|
||||
<string name="player_char_stam">Stamina</string>
|
||||
<string name="player_char_resistances">Resistances</string>
|
||||
<string name="player_char_phys">Physical</string>
|
||||
<string name="player_char_fire">Fire</string>
|
||||
<string name="player_char_cold">Cold</string>
|
||||
<string name="player_char_pois">Poison</string>
|
||||
<string name="player_char_energy">Energy</string>
|
||||
<string name="player_char_skills">Skills</string>
|
||||
<string name="player_char_points">Loyalty & Points</string>
|
||||
<!-- A point system's name followed by the character's rank on that board, e.g. "Queens Loyalty · #3". -->
|
||||
<string name="player_char_points_ranked">%1$s · #%2$d</string>
|
||||
<!-- A score against its cap. Only shown for capped systems; an uncapped score shows the number alone. -->
|
||||
<string name="player_char_points_of">%1$d / %2$d</string>
|
||||
<string name="player_char_equipment">Equipment</string>
|
||||
<string name="player_char_item">Item</string>
|
||||
<string name="player_char_item_id">id %1$d</string>
|
||||
<string name="player_char_item_hue">hue %1$d</string>
|
||||
|
||||
<!-- ── Player: vendors & sales (§6.3) ──────────────────────────── -->
|
||||
<string name="player_sales_title">Recent sales</string>
|
||||
<string name="player_sales_empty">No recent sales.</string>
|
||||
<string name="player_gold_amount">%1$s gp</string>
|
||||
<string name="player_vendors_no_accounts">Link a game account to see your vendors.</string>
|
||||
<string name="player_vendors_empty">No vendors on this account.</string>
|
||||
<string name="player_vendor_fallback">Player vendor</string>
|
||||
<string name="player_vendor_hold">Holding %1$s gp</string>
|
||||
<string name="player_vendor_no_listings">No items listed.</string>
|
||||
<string name="player_listing_item">Item %1$d ×%2$d</string>
|
||||
|
||||
<!-- ── Player: houses (§6.3) ───────────────────────────────────────── -->
|
||||
<string name="player_houses_empty">You don\'t have any houses on your linked accounts.</string>
|
||||
<string name="player_house_fallback">A house</string>
|
||||
<string name="player_house_stage">Status: %1$s</string>
|
||||
|
||||
<!-- ── Shard hub & live feed (§6.2) ─────────────────────────────── -->
|
||||
<string name="shard_live">Live</string>
|
||||
<string name="shard_offline">Offline</string>
|
||||
<string name="shard_status_online">Shard online</string>
|
||||
<string name="shard_status_offline">Shard offline</string>
|
||||
<string name="shard_online_count">%1$d players online</string>
|
||||
<string name="shard_economy_gold">Gold supply: %1$s</string>
|
||||
<string name="shard_section_staff">Staff online</string>
|
||||
<string name="shard_section_activity">Live activity</string>
|
||||
<string name="shard_feed_empty">No recent activity.</string>
|
||||
<string name="shard_nav_champs">Champion spawns</string>
|
||||
<string name="shard_nav_guilds">Guilds</string>
|
||||
<string name="shard_nav_governors">Governors</string>
|
||||
<string name="shard_nav_houses">Falling houses</string>
|
||||
|
||||
<!-- ── Champion spawns (§6.2) ──────────────────────────────────────── -->
|
||||
<string name="champs_empty">No champion spawns are being tracked right now.</string>
|
||||
<string name="champs_fallback_name">Champion spawn</string>
|
||||
<string name="champ_level">Level %1$d</string>
|
||||
<string name="champ_cooldown">Restarting</string>
|
||||
|
||||
<!-- ── Guilds (§6.2) ───────────────────────────────────────────────── -->
|
||||
<string name="guilds_empty">No guilds are being tracked right now.</string>
|
||||
<string name="guilds_fallback_name">Guild</string>
|
||||
<string name="guilds_members">%1$d members</string>
|
||||
<string name="guilds_leader">Led by %1$s</string>
|
||||
<string name="guilds_alliance">Alliance: %1$s</string>
|
||||
|
||||
<!-- ── Rules / ruleset (Protocol 3.0 §5, M11) ──────────────────────── -->
|
||||
<!-- A successful read of a shard that has never published its ruleset — NOT the same
|
||||
as the feature being switched off, which renders as an error state. -->
|
||||
<string name="rules_unpublished">This shard hasn\'t published its ruleset yet.</string>
|
||||
<string name="rules_section_shard">Shard</string>
|
||||
<string name="rules_section_systems">Systems</string>
|
||||
<string name="rules_section_caps">Skill & stat caps</string>
|
||||
<string name="rules_section_accounts">Accounts & housing</string>
|
||||
<string name="rules_section_vendors">Vendors</string>
|
||||
<string name="rules_section_schedule">Saves & restarts</string>
|
||||
<string name="rules_name">Name</string>
|
||||
<string name="rules_expansion">Expansion</string>
|
||||
<string name="rules_connect">Connect</string>
|
||||
<string name="rules_skill_cap">Individual skill cap</string>
|
||||
<string name="rules_total_skill_cap">Total skill cap</string>
|
||||
<string name="rules_stat_cap">Total stat cap</string>
|
||||
<string name="rules_str_cap">Strength cap</string>
|
||||
<string name="rules_dex_cap">Dexterity cap</string>
|
||||
<string name="rules_int_cap">Intelligence cap</string>
|
||||
<string name="rules_char_slots">Character slots</string>
|
||||
<string name="rules_per_ip">Accounts per IP</string>
|
||||
<string name="rules_house_limit">Houses per account</string>
|
||||
<string name="rules_restock_delay">Vendor restock delay</string>
|
||||
<string name="rules_max_sell">Max sell quantity</string>
|
||||
<string name="rules_autosave">Auto-save every</string>
|
||||
<string name="rules_autorestart">Auto-restart at</string>
|
||||
<string name="rules_minutes">%1$d min</string>
|
||||
|
||||
<!-- ── Leaderboards (Protocol 3.0 §7, M11) ─────────────────────────── -->
|
||||
<string name="leaderboards_empty">This shard isn\'t publishing any leaderboards yet.</string>
|
||||
<string name="leaderboards_board_empty">Nobody has scored here yet.</string>
|
||||
<!-- Where a score would sit on the placeholder row of an unscored board. An em
|
||||
dash, not "0" — nobody has scored zero, nobody has scored at all. -->
|
||||
<string name="leaderboards_no_score">—</string>
|
||||
<string name="leaderboards_players">%1$d players</string>
|
||||
<!-- Only shown for capped systems; most systems on a real shard are uncapped. -->
|
||||
<string name="leaderboards_cap">Cap: %1$d</string>
|
||||
<string name="leaderboards_rank_name">#%1$d %2$s</string>
|
||||
<!-- A shard may publish standings without naming who holds them (the board's one
|
||||
admin-configurable field). -->
|
||||
<string name="leaderboards_hidden_name">Someone</string>
|
||||
|
||||
<!-- ── Market (Protocol 3.0 §8, M11) ───────────────────────────────── -->
|
||||
<string name="market_search_label">Search every shop</string>
|
||||
<string name="market_empty">No listings match that search.</string>
|
||||
<string name="market_shop_empty">This shop has nothing for sale.</string>
|
||||
<!-- Required, not decoration: the shard sweeps vendors round-robin, so a price can
|
||||
legitimately be a full cycle old. -->
|
||||
<string name="market_staleness">Prices are refreshed in rotation and may be out of date.</string>
|
||||
<string name="market_price">%1$d gp</string>
|
||||
<string name="market_unnamed_item">Item %1$d</string>
|
||||
<string name="market_unnamed_shop">A shop</string>
|
||||
<string name="market_owner">Kept by %1$s</string>
|
||||
<!-- A gated location is a real answer: the shop exists, the shard just doesn't say
|
||||
where it stands. -->
|
||||
<string name="market_location_hidden">This shard doesn\'t publish shop locations.</string>
|
||||
<string name="market_truncated">Showing %1$d of %2$d — this shop holds more than the shard publishes.</string>
|
||||
|
||||
<!-- ── Spawn atlas (Protocol 3.0 §6, M11) ──────────────────────────── -->
|
||||
<string name="atlas_search_label">Search creatures</string>
|
||||
<string name="atlas_empty">No creatures match that search.</string>
|
||||
<!-- A place can legitimately hold a single spawner, and the aggregate list is full
|
||||
of them — "1 spawners" on every other row is worth a plural for. -->
|
||||
<plurals name="atlas_spawner_count">
|
||||
<item quantity="one">%1$d spawner</item>
|
||||
<item quantity="other">%1$d spawners</item>
|
||||
</plurals>
|
||||
<string name="atlas_total_alive">Up to %1$d alive at once</string>
|
||||
<string name="atlas_facet_count">%1$s (%2$d)</string>
|
||||
<!-- The aggregate: "where is it", as opposed to the raw coordinates below it. -->
|
||||
<string name="atlas_section_places">Where it spawns</string>
|
||||
<string name="atlas_place_max_alive">up to %1$d at once</string>
|
||||
<string name="atlas_section_spawners">Spawn points</string>
|
||||
<string name="atlas_section_also_here">Also spawns here</string>
|
||||
<string name="atlas_spawners_truncated">More spawn points than shown.</string>
|
||||
<string name="atlas_max_count">Up to %1$d</string>
|
||||
<!-- The delay is already in seconds; the server normalizes XmlSpawner's mixed units. -->
|
||||
<string name="atlas_respawn">Respawn %1$s</string>
|
||||
|
||||
<!-- ── Governors (§6.2) ────────────────────────────────────────────── -->
|
||||
<string name="governors_empty">No governors — this shard may not run the City Loyalty system.</string>
|
||||
<string name="governor_current">Governed by %1$s</string>
|
||||
<string name="governor_none">No governor</string>
|
||||
<string name="governor_election">Election: %1$s</string>
|
||||
<string name="governor_history">Term history</string>
|
||||
<string name="governor_history_loading">Loading…</string>
|
||||
<string name="governor_history_empty">No past terms recorded.</string>
|
||||
|
||||
<!-- ── Falling houses / IDOC (§6.2) ────────────────────────────────── -->
|
||||
<string name="houses_empty">No houses are in danger right now.</string>
|
||||
<string name="houses_fallback_name">A house</string>
|
||||
<string name="houses_idoc_badge">IDOC</string>
|
||||
|
||||
<!-- ── Push notifications (§11, M7 Part 2) ─────────────────────────── -->
|
||||
<string name="push_stream_server_status">Shard status changed</string>
|
||||
<string name="push_stream_idoc_warning">A house is falling (IDOC)</string>
|
||||
<string name="push_stream_champ_start">Champion spawn started</string>
|
||||
<string name="push_stream_governor_election">New governor elected</string>
|
||||
<string name="push_stream_vendor_sale">Your vendor made a sale</string>
|
||||
<string name="push_stream_house_idoc">Your house entered IDOC</string>
|
||||
<string name="push_stream_account_login">Login to your account</string>
|
||||
</resources>
|
||||
168
app/src/test/java/com/runicgateway/app/StringVocabularyTest.kt
Normal file
168
app/src/test/java/com/runicgateway/app/StringVocabularyTest.kt
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.runicgateway.app
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
import org.w3c.dom.Element
|
||||
import java.io.File
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
/**
|
||||
* The guard on the app's words (PLAN.md M20). A game's words stay on that game's
|
||||
* screens, and the screens every deployment shows use core's words. This is the
|
||||
* app's half of the rule the website docs and runicgateway.com already follow
|
||||
* (runicgateway.com PLAN.md §16, D63/D64).
|
||||
*
|
||||
* The strings are split by owner into three resource files that Android merges as
|
||||
* one `R.string`: `strings.xml` (core), `strings_uo.xml` and `strings_rust.xml`.
|
||||
* A string's owner is the screens that use it, not its key: a string any screen a
|
||||
* Rust deployment can reach is core, even when its key says `shard_`. This test
|
||||
* reads the three files and fails, naming the key and the word, when
|
||||
* - the core file holds either game's word;
|
||||
* - the UO file holds a Rust word;
|
||||
* - the Rust file holds a UO word.
|
||||
*
|
||||
* It checks every `values*` directory, so a translation is held to the same rule.
|
||||
* Comments are not read, because nobody using the app sees them.
|
||||
*/
|
||||
class StringVocabularyTest {
|
||||
|
||||
private enum class Owner(val file: String, val label: String) {
|
||||
CORE("strings.xml", "core"),
|
||||
UO("strings_uo.xml", "Ultima Online"),
|
||||
RUST("strings_rust.xml", "Rust"),
|
||||
}
|
||||
|
||||
private class Term(val game: Owner, val word: String, pattern: String) {
|
||||
val regex = Regex(pattern, RegexOption.IGNORE_CASE)
|
||||
}
|
||||
|
||||
/**
|
||||
* runicgateway.com's `scripts/checkTerms.mjs` list, word for word. Each word is a game's
|
||||
* name for something core names differently (shard → game server, guild and clan →
|
||||
* Team), or the name of one game's component. Game NAMES (Ultima Online, Rust) are not
|
||||
* on it, and neither is Steam, which is a core sign-in provider. Matching is on word
|
||||
* boundaries, so "wipe" does not match "wiped" and "shard" does not match `SHARD_X`.
|
||||
*/
|
||||
private val terms = listOf(
|
||||
Term(Owner.UO, "shard", """\bshards?\b"""),
|
||||
Term(Owner.UO, "ServUO", """\bservuo\b"""),
|
||||
Term(Owner.UO, "uo-link", """\buo-?link\b"""),
|
||||
Term(Owner.UO, "Module-uo", """\bmodule-uo\b"""),
|
||||
Term(Owner.UO, "Bridge.cfg", """\bbridge\.cfg\b"""),
|
||||
Term(Owner.UO, "cliloc", """\bclilocs?\b"""),
|
||||
Term(Owner.UO, "guild", """\bguilds?\b"""),
|
||||
Term(Owner.UO, "IDOC", """\bidocs?\b"""),
|
||||
Term(Owner.RUST, "rust-link", """\brust-?link\b"""),
|
||||
Term(Owner.RUST, "Rust-Plugins", """\brust-plugins\b"""),
|
||||
Term(Owner.RUST, "Module-Rust", """\bmodule-rust\b"""),
|
||||
Term(Owner.RUST, "Oxide", """\boxide\b"""),
|
||||
Term(Owner.RUST, "Carbon", """\bcarbon\b"""),
|
||||
Term(Owner.RUST, "uMod", """\bumod\b"""),
|
||||
Term(Owner.RUST, "clan", """\bclans?\b"""),
|
||||
Term(Owner.RUST, "wipe", """\bwipes?\b"""),
|
||||
)
|
||||
|
||||
/** The words [owner]'s file may not use. */
|
||||
private fun forbiddenIn(owner: Owner): List<Term> = when (owner) {
|
||||
Owner.CORE -> terms
|
||||
Owner.UO -> terms.filter { it.game == Owner.RUST }
|
||||
Owner.RUST -> terms.filter { it.game == Owner.UO }
|
||||
}
|
||||
|
||||
/** The words in [text] that [owner]'s file may not use. */
|
||||
private fun hits(owner: Owner, text: String): List<Pair<Term, String>> =
|
||||
forbiddenIn(owner).flatMap { term -> term.regex.findAll(text).map { term to it.value }.toList() }
|
||||
|
||||
/**
|
||||
* The module's `res`, wherever the test runs from: Gradle runs unit tests in the
|
||||
* module directory, and an IDE may run them from the repository root.
|
||||
*/
|
||||
private val res: File = listOf(File("src/main/res"), File("app/src/main/res"))
|
||||
.firstOrNull { it.isDirectory }
|
||||
?: error("Can't find src/main/res from ${File(".").absolutePath}")
|
||||
|
||||
private val valuesDirs: List<File>
|
||||
get() = res.listFiles { f -> f.isDirectory && f.name.startsWith("values") }!!.sortedBy { it.name }
|
||||
|
||||
/** Every resource in [file] that the app shows: key → its visible text. */
|
||||
private fun visibleText(file: File): List<Pair<String, String>> {
|
||||
val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file)
|
||||
val children = doc.documentElement.childNodes
|
||||
return (0 until children.length)
|
||||
.map { children.item(it) }
|
||||
.filterIsInstance<Element>()
|
||||
// A plural's items arrive as one text, which is all a word scan needs.
|
||||
.map { it.getAttribute("name") to it.textContent }
|
||||
}
|
||||
|
||||
@Test fun eachFileUsesOnlyItsOwnGamesWords() {
|
||||
val failures = mutableListOf<String>()
|
||||
for (dir in valuesDirs) {
|
||||
for (owner in Owner.entries) {
|
||||
val file = File(dir, owner.file)
|
||||
if (!file.exists()) continue
|
||||
for ((key, text) in visibleText(file)) {
|
||||
for ((term, found) in hits(owner, text)) {
|
||||
failures += "${dir.name}/${owner.file}: $key uses \"$found\", " +
|
||||
"${term.game.label} vocabulary, in the ${owner.label} file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failures.isNotEmpty()) {
|
||||
fail(
|
||||
failures.joinToString(
|
||||
separator = "\n ",
|
||||
prefix = "${failures.size} game word(s) outside their game's file:\n ",
|
||||
postfix = "\n\nPLAN.md M20: a string any deployment's screen shows is core, and uses " +
|
||||
"core's words (site, community, game server, Team). Reword it, or, if only one " +
|
||||
"game's screens use it, move it to that game's file.",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fourth strings file would be read by Android and skipped by this guard, so it
|
||||
* is refused. A new game gets its own file and its own row in [Owner].
|
||||
*/
|
||||
@Test fun everyStringsFileHasAnOwner() {
|
||||
val known = Owner.entries.map { it.file }.toSet()
|
||||
val strays = valuesDirs.flatMap { dir ->
|
||||
dir.listFiles { f -> f.name.startsWith("strings") && f.name !in known }!!.map { "${dir.name}/${it.name}" }
|
||||
}
|
||||
assertEquals("Strings files with no owner in StringVocabularyTest", emptyList<String>(), strays)
|
||||
}
|
||||
|
||||
/** Without this a wrong path would let the guard pass on nothing. */
|
||||
@Test fun theDefaultFilesAreAllThere() {
|
||||
for (owner in Owner.entries) {
|
||||
val file = File(res, "values/${owner.file}")
|
||||
assertTrue("${file.path} is missing", file.exists())
|
||||
assertTrue("${file.path} holds no strings", visibleText(file).isNotEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
/** The matcher itself, so a broken pattern can't make every file look clean. */
|
||||
@Test fun theWordListCatchesWhatItShould() {
|
||||
fun words(owner: Owner, text: String) = hits(owner, text).map { it.first.word }
|
||||
|
||||
assertEquals(listOf("shard"), words(Owner.CORE, "Connect to your shard"))
|
||||
assertEquals(listOf("guild"), words(Owner.CORE, "Guilds"))
|
||||
assertEquals(listOf("clan"), words(Owner.CORE, "You and your clan"))
|
||||
assertEquals(listOf("wipe"), words(Owner.UO, "Next wipe %1\$s"))
|
||||
assertEquals(listOf("IDOC"), words(Owner.RUST, "Your house entered IDOC"))
|
||||
// Each game's own words are fine in its own file.
|
||||
assertEquals(emptyList<String>(), words(Owner.UO, "Shard online"))
|
||||
assertEquals(emptyList<String>(), words(Owner.RUST, "Clan mate"))
|
||||
// Word boundaries: not a prefix, not an identifier.
|
||||
assertEquals(emptyList<String>(), words(Owner.CORE, "wiped %1\$s"))
|
||||
assertEquals(emptyList<String>(), words(Owner.CORE, "Guildmaster"))
|
||||
assertEquals(emptyList<String>(), words(Owner.CORE, "SHARD_VISIBILITY"))
|
||||
}
|
||||
}
|
||||
@@ -168,6 +168,26 @@ class MenuCapabilityGatingTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun moderationAndSupportNeedTheShardModule() {
|
||||
// Both call `/admin/shard/*`, module-uo's admin mount. Before M20 they
|
||||
// carried no capability, so a moderator on a Rust-only site saw a UO form
|
||||
// whose every action failed.
|
||||
val staffRows = APP_MENU.filter { it.route == Routes.ADMIN_MODERATION || it.route == Routes.ADMIN_SUPPORT }
|
||||
assertEquals(2, staffRows.size)
|
||||
assertTrue(staffRows.all { it.capability == Capability.SHARD })
|
||||
|
||||
val moderator = signedIn(Role.MODERATOR)
|
||||
val rustOnly = visibleEntries(APP_MENU, moderator, null, serving(Capability.RUST)).map { it.route }
|
||||
assertFalse(Routes.ADMIN_MODERATION in rustOnly)
|
||||
assertFalse(Routes.ADMIN_SUPPORT in rustOnly)
|
||||
// …while the core staff rows stay.
|
||||
assertTrue(Routes.ADMIN_DASHBOARD in rustOnly)
|
||||
|
||||
val uo = visibleEntries(APP_MENU, moderator, null, serving(Capability.SHARD)).map { it.route }
|
||||
assertTrue(Routes.ADMIN_MODERATION in uo)
|
||||
assertTrue(Routes.ADMIN_SUPPORT in uo)
|
||||
}
|
||||
|
||||
@Test fun bothEventRowsDeclareCoresCapabilityAndNoFeature() {
|
||||
// Events are core's. A `feature` on one of them would gate a core screen on
|
||||
// a module's visibility config, which is the coupling this separation exists
|
||||
|
||||
@@ -41,6 +41,35 @@ class RustFeedTest {
|
||||
assertTrue(line.detail.contains("42m"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a kill prints the weapon name the module sends`() {
|
||||
// Module-Rust#60: the module reads the game's own name off its table and
|
||||
// sends it beside the prefab, which stays the counting key.
|
||||
val line = describe(
|
||||
row(
|
||||
"player.death",
|
||||
"""{"name":"Bob","attackerType":"player","attackerName":"Alice",
|
||||
"weapon":"ak47u.entity","weaponName":"Assault Rifle"}""",
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals("with Assault Rifle", line.detail)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an older module's kill drops the prefab ending rather than printing it`() {
|
||||
// A module from before #60 sends no `weaponName`. The feed once said
|
||||
// "with ak47u entity"; the ending is dropped the way the website does it.
|
||||
val line = describe(
|
||||
row(
|
||||
"player.death",
|
||||
"""{"name":"Bob","attackerType":"player","attackerName":"Alice","weapon":"ak47u.entity"}""",
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals("with ak47u", line.detail)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a fall is a death by nobody, not a kill`() {
|
||||
// The failure this distinction exists to avoid: `HitInfo` is legitimately
|
||||
|
||||
@@ -117,6 +117,21 @@ class RustFormatTest {
|
||||
assertEquals("", prefabName(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a weapon prints the module's name, or the prefab without its ending`() {
|
||||
assertEquals("Assault Rifle", weaponName("ak47u.entity", "Assault Rifle"))
|
||||
// An older module sends no name; neither does one that sends a blank.
|
||||
assertEquals("ak47u", weaponName("ak47u.entity", null))
|
||||
assertEquals("ak47u", weaponName("ak47u.entity", ""))
|
||||
assertEquals("salvaged sword", weaponName("salvaged_sword.weapon", null))
|
||||
assertEquals("autoturret", weaponName("autoturret.deployed", null))
|
||||
assertEquals("rifle ak", weaponName("rifle.ak", null))
|
||||
// The ending is dropped only at the end.
|
||||
assertEquals("entity thing", weaponName("entity.thing", null))
|
||||
// No weapon, no name, even when a name came along.
|
||||
assertEquals("", weaponName(null, "Assault Rifle"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a nameless player shows a shortened id, not the word unknown`() {
|
||||
// A steam id is not a name and does not look like one, which is the point:
|
||||
|
||||
Reference in New Issue
Block a user