Skip to content

fix(auth): resolve session alert localization for non-English locales#11914

Open
jbaoho wants to merge 1 commit intoappwrite:1.9.xfrom
jbaoho:fix-session-alert-localization
Open

fix(auth): resolve session alert localization for non-English locales#11914
jbaoho wants to merge 1 commit intoappwrite:1.9.xfrom
jbaoho:fix-session-alert-localization

Conversation

@jbaoho
Copy link
Copy Markdown

@jbaoho jbaoho commented Apr 16, 2026

Problem

Session alert emails render raw {{emails.sessionAlert.*}} placeholders when a non-English locale is used.

Solution

  • Added missing localization terms for session alert templates.
  • Implemented translation normalization in sendSessionAlert().
  • Ensured fallback handling for unresolved placeholders.
  • Added an end-to-end regression test using the X-Appwrite-Locale header.

Testing

  • Ran docker compose exec appwrite test tests/e2e/Services/Account --filter=testSessionAlertWithLocaleHeader.

Fixes #8659

@jbaoho jbaoho marked this pull request as ready for review April 16, 2026 02:57
@jbaoho jbaoho force-pushed the fix-session-alert-localization branch from 1e92043 to 8dc4afd Compare April 16, 2026 04:19
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 16, 2026

Greptile Summary

Fixes session alert emails rendering raw {{emails.sessionAlert.*}} placeholders for non-English locales by registering the 10 missing terms in terms.json (so Crowdin can generate translations) and adding a $translate helper in Mails.php that detects {{key}} cross-references and re-resolves them. The existing setFallback('en') in the locale DI setup already handles truly missing keys, so the helper is a defensive layer primarily useful when a locale file contains explicit cross-reference stubs.

Confidence Score: 4/5

Safe to merge for the core fix; two P2 issues in the helper's fallback logic and test timing are worth addressing but do not block the primary regression.

The terms.json addition and switch to $translate correctly resolve placeholder leakage. The two P2 findings are quality concerns, not broken behaviour in current deployment configuration.

src/Appwrite/Bus/Listeners/Mails.php — $translate fallback logic; tests/e2e/Services/Account/AccountCustomClientTest.php — async email retrieval timing.

Important Files Changed

Filename Overview
app/config/locale/terms.json Adds 10 missing emails.sessionAlert.* terms to the Crowdin manifest; also fixes a missing newline at end of file.
src/Appwrite/Bus/Listeners/Mails.php Introduces $translate helper; fallback logic is a no-op for self-referencing stubs, and the greedy regex could mis-capture multi-group values.
app/controllers/api/account.php Trivial blank line removal; no logic changes.
tests/e2e/Services/Account/AccountCustomClientTest.php Adds E2E regression test with x-appwrite-locale: es; email retrieval immediately after session creation may be susceptible to async timing issues.

Reviews (1): Last reviewed commit: "fix(auth): resolve session alert localiz..." | Re-trigger Greptile

Comment on lines +65 to +73
$translate = static function (string $key) use ($locale): string {
$value = $locale->getText($key);

if (\preg_match('/^\{\{(.+)\}\}$/', $value, $matches) === 1) {
return $locale->getText($matches[1], $value);
}

return $value;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 $translate fallback is a no-op for self-references

When a locale file contains a self-referencing placeholder (e.g., "emails.sessionAlert.subject": "{{emails.sessionAlert.subject}}") — which can happen when Crowdin generates untranslated stubs — the function resolves $matches[1] to the same key it just failed to find. The second getText($matches[1], $value) call looks up the identical key, still finds nothing, and returns $value unchanged, so the raw placeholder leaks into the email.

At minimum, the greedy .+ in the regex should be changed to [^{}]+ to avoid mis-capturing values with multiple {{}} groups (e.g., {{a}}{{b}}):

Suggested change
$translate = static function (string $key) use ($locale): string {
$value = $locale->getText($key);
if (\preg_match('/^\{\{(.+)\}\}$/', $value, $matches) === 1) {
return $locale->getText($matches[1], $value);
}
return $value;
};
if (\preg_match('/^\{\{([^{}]+)\}\}$/', $value, $matches) === 1) {

Comment on lines +2204 to +2213

$this->assertEquals(201, $response['headers']['status-code']);

$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', $headers, [
'email' => $email,
'password' => $password,
]);

$this->assertEquals(201, $response['headers']['status-code']);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Email assertions may be brittle under slow async delivery

getLastEmailByAddress is called immediately after the second session creation with no wait or retry. If the Mails Bus listener is delayed (e.g., under load or queue backpressure), $lastEmail could be null or point to an earlier email, causing the assertStringContainsString assertions to fail spuriously. Consider using a poll/retry helper if one exists in the test framework.

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.

🐛 Bug Report: Session Alert template broken for all languages except English

1 participant