Skip to content

Add localizable metadata to ban notice#1085

Open
dinhkien0701 wants to merge 3 commits into
FAForever:developfrom
dinhkien0701:fix/issue-640-localizable-ban-notice
Open

Add localizable metadata to ban notice#1085
dinhkien0701 wants to merge 3 commits into
FAForever:developfrom
dinhkien0701:fix/issue-640-localizable-ban-notice

Conversation

@dinhkien0701
Copy link
Copy Markdown

@dinhkien0701 dinhkien0701 commented May 12, 2026

Summary

  • add localization metadata for ban notices (i18n_key, i18n_args) while keeping existing ext`n- wire ban notice localization metadata into BanError handling path
  • update integration tests for ban notice payload

Verification

  • python -m py_compile server/exceptions.py server/lobbyconnection.py tests/integration_tests/test_login.py tests/integration_tests/test_server.py
  • pytest not run in this environment (module missing)

Closes #640

Summary by CodeRabbit

  • New Features
    • Ban notice messages now include structured localization metadata with detailed information about ban duration, reason, and appeal contact details.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Warning

Rate limit exceeded

@dinhkien0701 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 17 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1eba5aec-8163-4f50-adae-af9175f5f643

📥 Commits

Reviewing files that changed from the base of the PR and between 0f1cbb5 and 7ab837e.

⛔ Files ignored due to path filters (1)
  • Pipfile.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • server/protocol/qdatastream.py
📝 Walkthrough

Walkthrough

This PR refactors ban message handling to separate rendered HTML from localizable message metadata. BanError now exposes structured i18n data via localization(), which LobbyConnection uses to construct a complete ban notice payload combining text and localization fields. Tests are updated to assert the new payload structure.

Changes

Ban message localization infrastructure

Layer / File(s) Summary
Ban error localization contract
server/exceptions.py
Constants BAN_NOTICE_I18N_KEY and BAN_APPEAL_EMAIL are added. BanError.localization() returns i18n metadata including key, duration, reason, and appeal email. BanError.message() now composes the HTML message from localization arguments instead of hardcoded values.
Lobby connection ban notice handling
server/lobbyconnection.py
on_message_received BanError handler builds a ban_notice dict combining message text with structured localization fields from e.localization(), sends the complete payload to the client, and aborts using the notice text.
Test assertions for ban notice payload
tests/integration_tests/test_login.py, tests/integration_tests/test_server.py
Three ban-related tests update expected notice assertions to include i18n_key: "notice.ban" and i18n_args (duration, reason, appeal_email) alongside the rendered text.

🎯 2 (Simple) | ⏱️ ~12 minutes

🐰 Hop into locales, the bans now speak,
No hardcoded English, translation's unique,
Metadata flows from server to client screen,
Multilingual moderation—fair, clean!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR partially addresses issue #640 by adding localization metadata (i18n_key and i18n_args), but does not fully implement the proposed structured format with ISO timestamps for ban expiry. Clarify whether the partial implementation meets the requirements or if additional changes are needed to fully replace English strings with localized metadata and use ISO timestamps for expiration.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding localization metadata (i18n_key and i18n_args) to ban notices, which is the main objective across all modified files.
Out of Scope Changes check ✅ Passed All changes are directly related to the PR objective of adding localization metadata to ban notices; no unrelated modifications were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/exceptions.py (1)

47-54: ⚡ Quick win

Optimize to avoid computing localization data twice.

localization() is called here to build the message text, then called again in lobbyconnection.py (line 229) when constructing the notice payload. This means _ban_duration_text() runs twice per ban event, performing redundant datetime calculations.

Consider caching the localization data or refactoring to compute it once.

💡 Example approach

Option 1: Cache the localization result on the instance.

+def __init__(self, ban_expiry, ban_reason, *args, **kwargs):
+    super().__init__(*args, **kwargs)
+    self.ban_expiry = ban_expiry
+    self.ban_reason = ban_reason
+    self._localization_cache = None
+
 def localization(self):
+    if self._localization_cache is None:
+        self._localization_cache = {
+            "i18n_key": BAN_NOTICE_I18N_KEY,
+            "i18n_args": {
+                "duration": self._ban_duration_text(),
+                "reason": self.ban_reason,
+                "appeal_email": BAN_APPEAL_EMAIL
+            }
+        }
+    return self._localization_cache
-    return {
-        "i18n_key": BAN_NOTICE_I18N_KEY,
-        "i18n_args": {
-            "duration": self._ban_duration_text(),
-            "reason": self.ban_reason,
-            "appeal_email": BAN_APPEAL_EMAIL
-        }
-    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/exceptions.py` around lines 47 - 54, The message() property calls
localization() and duplicates work already needed by lobbyconnection; modify the
Ban/exception class to compute and cache localization() once (e.g., in __init__
or first access) and have message(), localization(), and any external callers
like lobbyconnection.py use that cached result; specifically, add a private
attribute (e.g., self._localization_cache) populated by a new method or in
__init__, have localization() return the cached value, and update message() to
read from that cache so that _ban_duration_text() and related datetime work run
only once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/exceptions.py`:
- Around line 37-45: The localization payload currently uses a pre-rendered
"duration" string from _ban_duration_text(); replace that with an ISO8601 UTC
timestamp field named expires_at (or null for permanent bans) so clients can
compute/localize durations. In the localization() method, stop including
"duration" and instead include "expires_at": compute the ban end as a
timezone-aware datetime in UTC (e.g., self.expires_at or derive from existing
ban end property), format it with .astimezone(timezone.utc).isoformat() (or
append 'Z' for UTC) and set None for permanent/forever bans; keep other keys
(BAN_NOTICE_I18N_KEY, ban_reason, BAN_APPEAL_EMAIL) unchanged and remove calls
to _ban_duration_text().

---

Nitpick comments:
In `@server/exceptions.py`:
- Around line 47-54: The message() property calls localization() and duplicates
work already needed by lobbyconnection; modify the Ban/exception class to
compute and cache localization() once (e.g., in __init__ or first access) and
have message(), localization(), and any external callers like lobbyconnection.py
use that cached result; specifically, add a private attribute (e.g.,
self._localization_cache) populated by a new method or in __init__, have
localization() return the cached value, and update message() to read from that
cache so that _ban_duration_text() and related datetime work run only once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ba4b1f4-91a4-461e-b1f6-778ec94bbe01

📥 Commits

Reviewing files that changed from the base of the PR and between e36de71 and 0f1cbb5.

📒 Files selected for processing (4)
  • server/exceptions.py
  • server/lobbyconnection.py
  • tests/integration_tests/test_login.py
  • tests/integration_tests/test_server.py

Comment thread server/exceptions.py
Comment on lines +37 to +45
def localization(self):
return {
"i18n_key": BAN_NOTICE_I18N_KEY,
"i18n_args": {
"duration": self._ban_duration_text(),
"reason": self.ban_reason,
"appeal_email": BAN_APPEAL_EMAIL
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Include expires_at as an ISO timestamp instead of humanized duration text.

Issue #640 explicitly requires "timestamps are ISO-formatted in UTC (not timedeltas)" to enable client-side localization. The current implementation sends duration as pre-rendered English text (e.g., "for 3 hours" or "forever"), which prevents the client from localizing the duration into the user's language.

Replace duration with expires_at so the client can compute and localize the duration based on the user's locale and current time.

♻️ Proposed refactor
 def localization(self):
+    ban_expiry_iso = None if self.ban_expiry.year > 9000 else self.ban_expiry.isoformat()
     return {
         "i18n_key": BAN_NOTICE_I18N_KEY,
         "i18n_args": {
-            "duration": self._ban_duration_text(),
+            "expires_at": ban_expiry_iso,
             "reason": self.ban_reason,
             "appeal_email": BAN_APPEAL_EMAIL
         }
     }

Note: Permanent bans (year > 9000) can use None or a sentinel value to indicate "forever" rather than a far-future timestamp.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/exceptions.py` around lines 37 - 45, The localization payload
currently uses a pre-rendered "duration" string from _ban_duration_text();
replace that with an ISO8601 UTC timestamp field named expires_at (or null for
permanent bans) so clients can compute/localize durations. In the localization()
method, stop including "duration" and instead include "expires_at": compute the
ban end as a timezone-aware datetime in UTC (e.g., self.expires_at or derive
from existing ban end property), format it with
.astimezone(timezone.utc).isoformat() (or append 'Z' for UTC) and set None for
permanent/forever bans; keep other keys (BAN_NOTICE_I18N_KEY, ban_reason,
BAN_APPEAL_EMAIL) unchanged and remove calls to _ban_duration_text().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace ban text with localizable message

1 participant