Skip to content

fix: App slash commands show UUID keys instead of descriptions#7072

Open
divyanshu-patil wants to merge 5 commits intoRocketChat:developfrom
divyanshu-patil:fix/commands-description
Open

fix: App slash commands show UUID keys instead of descriptions#7072
divyanshu-patil wants to merge 5 commits intoRocketChat:developfrom
divyanshu-patil:fix/commands-description

Conversation

@divyanshu-patil
Copy link
Copy Markdown

@divyanshu-patil divyanshu-patil commented Mar 30, 2026

Proposed changes

Slash commands registered by Rocket.Chat Apps (e.g. Jitsi, Giphy) have their descriptions stored as dynamic i18n keys in the format app-<uuid>.command_description. These keys are injected at runtime on the web client via TAPi18next but were never exposed to the React Native client, causing the raw translation key to be displayed instead of the human-readable description.
This PR introduces a new server-side REST endpoint (GET /api/v1/apps.translations) that aggregates translation strings from all installed and enabled Apps, and a client-side pipeline that fetches, persists, and resolves these translations in WatermelonDB.

Depends on

RocketChat/Rocket.Chat#39959

Issue(s)

closes #7071

How to test or reproduce

  1. Install any Rocket.Chat App that registers a slash command (e.g. Jitsi Video Call)
  2. Open the React Native mobile app and navigate to any room
  3. Tap the message input and type /
  4. Observe the autocomplete list of slash commands

Screenshots

Before After
before after
English Russian Portuguese
English russian portuguese

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features
    • Slash command descriptions are now translated and shown in autocomplete based on your language.
    • App translations are fetched on login and when you change language, keeping interface copy up to date.
    • Improved multilingual support across commands and UI content for a more consistent localized experience.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8907d378-60be-4c3b-ac7f-49e89e4c525a

📥 Commits

Reviewing files that changed from the base of the PR and between 9b5517c and 806b08c.

📒 Files selected for processing (1)
  • app/sagas/login.js
✅ Files skipped from review due to trivial changes (1)
  • app/sagas/login.js

Walkthrough

Adds app translation support: new WatermelonDB table and model, REST endpoint type, a fetch-and-cache method, automatic fetching on login/language change, and translation lookup in slash-command autocomplete with fallbacks.

Changes

Cohort / File(s) Summary
Database Schema & Model
app/lib/database/schema/app.js, app/lib/database/model/migrations.js, app/lib/database/model/AppTranslation.ts, app/lib/database/index.ts
Introduce app_translations table (schema v29) with indexed key and language; add WatermelonDB model AppTranslation and register it in database initialization.
REST API Definitions
app/definitions/rest/v1/appTranslations.ts, app/definitions/rest/v1/index.ts
Add AppsTranslationsEndpoints type describing GET apps.translations?language returning { language, translations }; extend exported Endpoints union.
Translation Fetching & Caching
app/lib/methods/getAppTranslations.ts
New getAppTranslations(language) method: calls SDK apps.translations, and on success replaces existing language rows with new app_translations records inside a DB write transaction using stable ids.
Login & Language Integration
app/sagas/login.js, app/views/LanguageView/index.tsx
Trigger getAppTranslations after login and after language selection (deriving base language like en from en-US), errors are logged and caught by existing handlers.
Slash Command Autocomplete
app/containers/MessageComposer/hooks/useAutocomplete.ts
Load app_translations collection and resolve command subtitle by: using description if present; if appId present, lookup translation by (key=description, language=appLang) and use translated value or fall back to readable key; set items preserving focus/visibility logic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LoginSaga as Login Saga
    participant SDK
    participant DB as WatermelonDB
    participant Composer as MessageComposer

    User->>LoginSaga: successful login / language change
    LoginSaga->>SDK: GET apps.translations?language=xx
    SDK-->>LoginSaga: { language, translations }
    LoginSaga->>DB: write transaction (delete old rows, create new app_translations)
    DB-->>LoginSaga: ack

    User->>Composer: types "/"
    Composer->>DB: query app_translations where language=xx and key=description_key
    DB-->>Composer: translation record or none
    Composer-->>User: display command list with resolved subtitles (translated or fallback)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main fix: addressing slash commands from Rocket.Chat Apps displaying UUID keys instead of human-readable descriptions.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #7071 by implementing a complete pipeline: new REST endpoint for app translations, WatermelonDB schema changes, translation fetching on login and language change, and slash command description resolution using fetched translations.
Out of Scope Changes check ✅ Passed All code changes are directly related to delivering app translations to the React Native client and resolving command descriptions; no unrelated modifications detected.

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


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.

@divyanshu-patil divyanshu-patil changed the title fix: fix: App slash commands show UUID keys instead of descriptions Mar 30, 2026
Copy link
Copy Markdown
Contributor

@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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/containers/MessageComposer/hooks/useAutocomplete.ts`:
- Around line 155-161: In the useAutocomplete.ts branch handling command.appId,
change the lookup to first query appTranslationsCollection using the full
I18n.currentLocale() (the same locale key used when saving in
app/lib/methods/getSlashCommands.ts) and only if that returns no records, fall
back to querying with the stripped base language (appLang =
I18n.currentLocale().split('-')[0]); update the logic around variables
translationRecords and appLang so the primary fetch uses I18n.currentLocale()
and the secondary uses the base language to ensure saved translations like
"en-US" or "pt-BR" are found before falling back to the generic language key.

In `@app/lib/methods/getAppTranslations.ts`:
- Around line 42-51: The current logic in getAppTranslations uses
collection.query(Q.where('language', language)).fetchCount() and returns as soon
as any row exists, making app_translations write-once and never refreshed;
change this to detect staleness instead of presence: replace the
fetchCount-based early return with either (a) always call
fetchAndSaveTranslations(language, db) so translations are refreshed on each
sync, or (b) fetch the existing rows (collection.query(...).fetch()) and inspect
a freshness/version/timestamp field (e.g., updated_at or app_version) to decide
when to call fetchAndSaveTranslations; ensure fetchAndSaveTranslations performs
upserts (not inserts-only) so it can update existing records when new data
arrives.

In `@app/lib/methods/getSlashCommands.ts`:
- Around line 23-25: The prefetch of translations via
getAppTranslations(I18n.currentLocale()) must not abort the whole slash-command
sync if it fails; wrap the getAppTranslations call in its own try/catch (or
attach a .catch) and log or ignore the error so it does not propagate to the
outer catch that handles command persistence, then continue with the existing
slash-command sync/persistence logic (the code that persists slash commands
after the translation call). Ensure you reference getAppTranslations and the
surrounding slash-command sync/persistence block so only the translation fetch
is isolated.
- Around line 24-25: getSlashCommands.ts calls getAppTranslations with
I18n.currentLocale() (e.g. "en-US") which mismatches LanguageView's
base-language keys (e.g. "en"), causing cache misses; change the call to
normalize the locale to the base language via LanguageView before calling
getAppTranslations (e.g. derive base language from I18n.currentLocale() using
LanguageView’s helper or mapping and pass that base value to getAppTranslations)
so keys align with LanguageView and avoid duplication/unresolved descriptions.

In `@app/views/LanguageView/index.tsx`:
- Around line 86-88: The catch currently groups getAppTranslations() failures
with saveUserPreferences() failures; split these operations so translation fetch
errors are handled separately: call saveUserPreferences(...) and await it in its
own try/catch (or let it propagate to the existing success/failure flow), then
call getAppTranslations(appLang) in a separate try/catch block and handle/log
translation-specific errors (using the params.language-derived appLang and
getAppTranslations) without triggering the “saving preferences” error path or
telemetry for saveUserPreferences.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 716a7016-bd26-431e-964e-8918b5b6cea7

📥 Commits

Reviewing files that changed from the base of the PR and between b71ee89 and 30a4410.

📒 Files selected for processing (10)
  • app/containers/MessageComposer/hooks/useAutocomplete.ts
  • app/definitions/rest/v1/appTranslations.ts
  • app/definitions/rest/v1/index.ts
  • app/lib/database/index.ts
  • app/lib/database/model/AppTranslation.ts
  • app/lib/database/model/migrations.js
  • app/lib/database/schema/app.js
  • app/lib/methods/getAppTranslations.ts
  • app/lib/methods/getSlashCommands.ts
  • app/views/LanguageView/index.tsx
📜 Review details
🔇 Additional comments (6)
app/definitions/rest/v1/appTranslations.ts (1)

1-8: Endpoint typing looks consistent with the new translations flow.
This definition cleanly exposes the apps.translations contract for callers.

app/definitions/rest/v1/index.ts (1)

24-24: Endpoints composition update is correct.
The new endpoint group is properly registered in the global REST type map.

Also applies to: 48-49

app/lib/database/model/AppTranslation.ts (1)

4-10: Model definition aligns with the new DB table contract.
table name and fields match the migration/schema introduced in this PR.

app/lib/database/model/migrations.js (1)

349-360: Migration step is well-formed and versioned correctly.
The new table creation matches the intended translation cache persistence flow.

app/lib/database/index.ts (1)

28-28: Database model registration is correctly integrated.
AppTranslation is now available to WatermelonDB queries/writes in app scope.

Also applies to: 64-65

app/lib/database/schema/app.js (1)

4-4: Schema update is consistent with migration/model changes.
Version bump and app_translations table definition are in sync.

Also applies to: 289-296

Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/sagas/login.js`:
- Around line 188-195: The fetchAppTranslationsFork currently reads locale from
I18n.currentLocale() before the user's preference is applied; change
fetchAppTranslationsFork to accept a language parameter (e.g.,
fetchAppTranslationsFork(lang)) and use that value when calling
getAppTranslations (falling back to I18n.currentLocale().split('-')[0] if lang
is falsy). Then update the place that forks it (the fork call near where
setLanguage(user?.language) is invoked) to pass user?.language so translations
are fetched using the stored user preference rather than the pre-update I18n
locale.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 57587415-9c62-4ef6-92ea-8df913596d72

📥 Commits

Reviewing files that changed from the base of the PR and between 30a4410 and 0b011a1.

📒 Files selected for processing (3)
  • app/lib/methods/getAppTranslations.ts
  • app/lib/methods/getSlashCommands.ts
  • app/sagas/login.js
✅ Files skipped from review due to trivial changes (1)
  • app/lib/methods/getSlashCommands.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/lib/methods/getAppTranslations.ts
📜 Review details
🔇 Additional comments (2)
app/sagas/login.js (2)

42-42: LGTM!

Import follows the existing pattern and correctly references the getAppTranslations method.


264-264: Fork placement follows established pattern.

The fork is correctly placed alongside other data-fetching operations. The graceful fallback in useAutocomplete.ts (per context snippet 3) handles cases where translations haven't arrived yet by showing a readable form of the description key.

Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
app/sagas/login.js (1)

180-188: ⚠️ Potential issue | 🟠 Major

Language may be captured before user preference is applied.

fetchSlashCommandsFork is forked at line 256, but setLanguage(user?.language) executes at line 264. At line 182, I18n.currentLocale() may not reflect the user's stored preference yet, causing incorrect translations to be fetched and cached.

Pass the user's language directly to avoid the race:

Proposed fix
-const fetchSlashCommandsFork = function* fetchSlashCommandsFork() {
+const fetchSlashCommandsFork = function* fetchSlashCommandsFork(userLanguage) {
 	try {
-		const appLang = I18n.currentLocale().split('-')[0];
+		const appLang = (userLanguage || I18n.currentLocale()).split('-')[0];
 		yield getAppTranslations(appLang);
 		yield getSlashCommands();
 	} catch (e) {
 		log(e);
 	}
 };

Then update the fork call at line 256:

-		yield fork(fetchSlashCommandsFork);
+		yield fork(fetchSlashCommandsFork, user?.language);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sagas/login.js` around lines 180 - 188, fetchSlashCommandsFork currently
reads I18n.currentLocale() which can race with setLanguage(user?.language);
change fetchSlashCommandsFork to accept a language parameter (e.g.,
fetchSlashCommandsFork(language)) and use that to call
getAppTranslations(language) before getSlashCommands, then update the place
where it's forked (the fork that currently calls fetchSlashCommandsFork) to pass
the user's stored language (user?.language or a safe fallback) after setLanguage
is determined; keep function name fetchSlashCommandsFork and calls to
getAppTranslations and getSlashCommands unchanged besides using the passed-in
language.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/sagas/login.js`:
- Around line 180-188: fetchSlashCommandsFork currently reads
I18n.currentLocale() which can race with setLanguage(user?.language); change
fetchSlashCommandsFork to accept a language parameter (e.g.,
fetchSlashCommandsFork(language)) and use that to call
getAppTranslations(language) before getSlashCommands, then update the place
where it's forked (the fork that currently calls fetchSlashCommandsFork) to pass
the user's stored language (user?.language or a safe fallback) after setLanguage
is determined; keep function name fetchSlashCommandsFork and calls to
getAppTranslations and getSlashCommands unchanged besides using the passed-in
language.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55944ced-26f1-4178-9e77-a651faed65cb

📥 Commits

Reviewing files that changed from the base of the PR and between 0b011a1 and 7af6f12.

📒 Files selected for processing (1)
  • app/sagas/login.js
📜 Review details
🔇 Additional comments (1)
app/sagas/login.js (1)

42-42: LGTM!

Import is correctly added for the new getAppTranslations method.

@divyanshu-patil divyanshu-patil force-pushed the fix/commands-description branch from 7af6f12 to 9b5517c Compare March 30, 2026 08:43
Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
app/sagas/login.js (1)

188-195: ⚠️ Potential issue | 🟠 Major

Fetch App translations using the user’s language, not pre-login locale.

At Line 190, language is read from I18n.currentLocale() before Line 272 applies setLanguage(user?.language). On devices where default locale differs from profile locale, translations are fetched under the wrong language and autocomplete lookup can miss.

Proposed fix
-const fetchAppTranslationsFork = function* fetchAppTranslationsFork() {
+const fetchAppTranslationsFork = function* fetchAppTranslationsFork(userLanguage) {
 	try {
-		const appLang = I18n.currentLocale().split('-')[0];
+		const appLang = (userLanguage || I18n.currentLocale()).split('-')[0];
 		yield getAppTranslations(appLang);
 	} catch (e) {
 		log(e);
 	}
 };
@@
-		yield fork(fetchAppTranslationsFork);
+		yield fork(fetchAppTranslationsFork, user?.language);

Also applies to: 263-263

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sagas/login.js` around lines 188 - 195, fetchAppTranslationsFork
currently reads language from I18n.currentLocale() (causing translations to be
fetched before the logged-in user's language is applied); change
fetchAppTranslationsFork (and the other occurrence using I18n.currentLocale())
to use the authenticated user's language instead—e.g., read user?.language from
the auth state (or accept a language param) and call
getAppTranslations(userLanguage) after setLanguage(user?.language) has run;
ensure getAppTranslations is invoked with that user language rather than
I18n.currentLocale().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/sagas/login.js`:
- Around line 188-195: fetchAppTranslationsFork currently reads language from
I18n.currentLocale() (causing translations to be fetched before the logged-in
user's language is applied); change fetchAppTranslationsFork (and the other
occurrence using I18n.currentLocale()) to use the authenticated user's language
instead—e.g., read user?.language from the auth state (or accept a language
param) and call getAppTranslations(userLanguage) after
setLanguage(user?.language) has run; ensure getAppTranslations is invoked with
that user language rather than I18n.currentLocale().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f0ae6e98-62da-447d-91ad-00cce477c973

📥 Commits

Reviewing files that changed from the base of the PR and between 7af6f12 and 9b5517c.

📒 Files selected for processing (1)
  • app/sagas/login.js
📜 Review details
🔇 Additional comments (1)
app/sagas/login.js (1)

42-42: Import wiring looks good.

getAppTranslations is correctly imported and used by the new login-success fork flow.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Slash commands from Rocket.Chat Apps show raw i18n key instead of description

1 participant