Skip to content

fix: the translation on the filter options#7667

Closed
gulshank0 wants to merge 2 commits intoformbricks:mainfrom
gulshank0:issue/7653
Closed

fix: the translation on the filter options#7667
gulshank0 wants to merge 2 commits intoformbricks:mainfrom
gulshank0:issue/7653

Conversation

@gulshank0
Copy link
Copy Markdown
Contributor

What does this PR do?

This PR solves the " Filter criteria as translatable "
Fixed: Added proper native translations to all languages.

Fixes #7653

Previously:

Screenshot_03-Apr_16-14-19_29149

After Fix:
Screenshot_03-Apr_16-14-53_6269

How should this be tested?

Screenshot_03-Apr_16-21-42_12591
  • pnpm build

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read How we Code at Formbricks
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand bits
  • Ran pnpm build
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues
  • First PR at Formbricks? Please sign the CLA! Without it we wont be able to merge it 🙏

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Formbricks Docs if changes were necessary

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

Walkthrough

The pull request adds internationalization (i18n) support to survey filtering functionality. The generateElementAndFilterOptions function signature was updated to accept a translation function parameter t. Hardcoded English labels for filter options, condition operators, and meta operator dropdowns were replaced with localized string translations. Helper functions getConditionOptions(t), getFilterOptions(t), and getMetaOpMap(t) were introduced to generate translated labels. Test cases were updated to pass the translation function and verify that translation keys are expected instead of hardcoded English strings. The component using these functions was modified to pass the translation function and include it in effect dependencies.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: the translation on the filter options' clearly describes the main change - adding translation support to filter options in the UI.
Description check ✅ Passed The PR description provides issue reference (#7653), before/after screenshots, testing instructions, and confirms all required checklist items including self-review and build verification.
Linked Issues check ✅ Passed The PR successfully addresses issue #7653 by converting hardcoded English filter condition/option labels and META_OP_MAP entries into translatable strings using the i18n TFunction across all three modified files.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing i18n support for filter criteria in surveys as specified in issue #7653; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/web/app/lib/surveys/surveys.ts (3)

538-566: ⚠️ Potential issue | 🔴 Critical

Critical: META_OP_MAP is undefined at module scope, causing runtime error.

processMetaFilters references META_OP_MAP at line 551, but META_OP_MAP is now only defined inside generateElementAndFilterOptions (line 157). This will cause a ReferenceError at runtime when any meta filter is applied.

Proposed fix: Define META_OP_MAP at module scope or pass it as parameter

Option 1: Keep a module-level map for logic comparisons (using English keys for internal logic):

+// Internal operation mapping - uses English keys for logic comparisons
+const META_OP_MAP = {
+  Equals: "equals",
+  "Not equals": "notEquals",
+  Contains: "contains",
+  "Does not contain": "doesNotContain",
+  "Starts with": "startsWith",
+  "Does not start with": "doesNotStartWith",
+  "Ends with": "endsWith",
+  "Does not end with": "doesNotEndWith",
+} as const;

Option 2: Pass the map as a parameter to processMetaFilters.

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

In `@apps/web/app/lib/surveys/surveys.ts` around lines 538 - 566,
processMetaFilters currently references META_OP_MAP which is defined only inside
generateElementAndFilterOptions, causing a ReferenceError; fix by either (A)
defining META_OP_MAP at module scope so processMetaFilters can access it, or (B)
add a parameter (e.g., metaOpMap) to processMetaFilters and pass the existing
META_OP_MAP from generateElementAndFilterOptions (update all callers
accordingly); ensure you update the function signature of processMetaFilters and
every call site (including generateElementAndFilterOptions) or move the constant
above the function definition so the symbol is available at module scope.

302-403: ⚠️ Potential issue | 🔴 Critical

Critical: Filter logic still compares against hardcoded English strings, breaking i18n.

The UI now displays translated filter values (e.g., t("environments.surveys.filter.filled_out")), but these helper functions still compare against hardcoded English strings like "Filled out", "Skipped", "Clicked", "Dismissed", "Accepted", "Includes either", "Includes all", "Is equal to", etc.

When a non-English user selects a filter, the translated string will not match these hardcoded comparisons, causing filters to silently fail.

You need a translation strategy that maintains internal identifiers while translating at the UI layer. Consider using stable keys/identifiers for logic and only translating at display time.

Suggested approach: Use stable identifiers for logic

Instead of comparing translated UI strings, use stable operation keys:

-const processFilledOutSkippedFilter = (
-  filterType: FilterValue["filterType"],
-  elementId: string,
-  filters: TResponseFilterCriteria
-) => {
-  if (filterType.filterComboBoxValue === "Filled out") {
-    filters.data![elementId] = { op: "filledOut" };
-  } else if (filterType.filterComboBoxValue === "Skipped") {
-    filters.data![elementId] = { op: "skipped" };
-  }
-};
+// Use a mapping from translated labels to operation keys
+const FILTER_VALUE_TO_OP = {
+  filledOut: "filledOut",
+  skipped: "skipped",
+  // ... etc
+} as const;
+
+// Store the operation key alongside the translated label in filterComboBoxValue
+// or use a separate field for the operation identifier

The filter options should store both the display label (translated) and a stable identifier for logic comparisons.

Based on learnings: "In apps/web/app/lib/surveys/surveys.ts, the conditionOptions and filterOptions objects contain hardcoded user-facing strings... that need to be marked as translatable. These values are used both for UI display in ElementFilterComboBox and for logic comparisons in getFormattedFilters, requiring a translation strategy that maintains internal identifiers while translating at the UI layer."

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

In `@apps/web/app/lib/surveys/surveys.ts` around lines 302 - 403, The helper
functions (processFilledOutSkippedFilter, processRankingFilter,
processMultipleChoiceFilter, processNPSRatingFilter, processCTAFilter,
processConsentFilter) are comparing against hardcoded English labels which
breaks i18n; change the filter option shape so UI displays translated labels but
also carries a stable identifier (e.g., add fields like optionId / valueId /
comboId on conditionOptions/filterOptions and ensure ElementFilterComboBox
passes those ids into getFormattedFilters), then update each helper to switch on
these stable identifiers instead of filterType.filterComboBoxValue or
filterType.filterValue (and still parse ints for NPS/rating when the identifier
expects numeric values). Ensure UI uses the label for display only and logic
uses the stable id for comparisons.

405-431: ⚠️ Potential issue | 🔴 Critical

Critical: Picture selection filter parsing breaks with translated labels.

Line 421 parses the picture index using option.split(" ")[1], assuming "Picture N" format. However, the translated label t("environments.surveys.filter.picture_idx", { idx: idx + 1 }) may produce different formats across languages (e.g., "Bild 1" in German, "Image 1" in French), breaking this parsing logic.

Proposed fix: Store index alongside translated label

Store the picture index as data rather than parsing it from the display string:

 case TSurveyElementTypeEnum.PictureSelection:
   return {
     ...baseOption,
-    filterComboBoxOptions: element.choices?.map((_, idx) =>
-      t("environments.surveys.filter.picture_idx", { idx: idx + 1 })
-    ) ?? [""],
+    filterComboBoxOptions: element.choices?.map((choice, idx) => ({
+      label: t("environments.surveys.filter.picture_idx", { idx: idx + 1 }),
+      id: choice.id,
+      index: idx,
+    })) ?? [],
   };

Then update processPictureSelectionFilter to use the stored index/id instead of parsing.

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

In `@apps/web/app/lib/surveys/surveys.ts` around lines 405 - 431,
processPictureSelectionFilter currently parses the choice index from the
translated display string (option.split(" ")[1]) which breaks localization;
instead store the choice index or id when building the combo options (e.g.,
include an explicit value/index field on each filterComboBoxValue entry) and
update processPictureSelectionFilter to read that stored index/id (from
filterType.filterComboBoxValue[*].value or similar) to map to element.choices
(use element?.choices[index]?.id or the stored choice id) so parsing of
translated labels is eliminated. Ensure the code references
processPictureSelectionFilter, filterType.filterComboBoxValue, and
getElementsFromBlocks element.
apps/web/app/lib/surveys/surveys.test.ts (1)

565-584: 🧹 Nitpick | 🔵 Trivial

Note: Tests use English strings matching current (broken) implementation.

The filterComboBoxValue: "Filled out" at line 577 tests against the current implementation where processFilledOutSkippedFilter compares against hardcoded English "Filled out". Once the implementation is fixed to use stable identifiers, these test inputs will need updating.

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

In `@apps/web/app/lib/surveys/surveys.test.ts` around lines 565 - 584, The test is
using the English string "Filled out" which matches the old broken
implementation; update the selectedFilter fixture so
filter.filter[0].filterType.filterComboBoxValue uses the stable identifier the
code now expects (e.g., the enum/value used by processFilledOutSkippedFilter
such as "filledOut" or FilterComboBoxValue.FilledOut) for the openTextQ case so
getFormattedFilters(survey, selectedFilter, ...) produces { op: "filledOut" } as
asserted; adjust the selectedFilter construction in this test (and any similar
tests) to reference that stable identifier rather than the English label.
♻️ Duplicate comments (1)
apps/web/app/lib/surveys/surveys.ts (1)

568-587: ⚠️ Potential issue | 🔴 Critical

Critical: Quota filter logic uses hardcoded English strings.

The statusMap at lines 574-578 contains hardcoded English strings ("Screened in", "Screened out (overquota)", "Not in quota"), but the UI now displays translated versions. This will break quota filtering for non-English users.

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

In `@apps/web/app/lib/surveys/surveys.ts` around lines 568 - 587,
processQuotaFilters uses hardcoded, localized strings in statusMap which breaks
for translations; change it to map from an unlocalized identifier (e.g., the
combo value id/key) instead of the displayed text. In the processQuotaFilters
function, replace the statusMap keys that use
String(filterType.filterComboBoxValue) with the stable identifier field on the
filter combo value (for example filterType.filterComboBoxValueId or
filterType.filterComboBoxValue.key), update the lookup to use that id (e.g.,
const op = statusMap[filterType.filterComboBoxValueId]) and ensure you keep a
safe fallback (no-op) if the id is missing; this preserves the existing behavior
while removing reliance on translated strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/web/app/lib/surveys/surveys.test.ts`:
- Around line 565-584: The test is using the English string "Filled out" which
matches the old broken implementation; update the selectedFilter fixture so
filter.filter[0].filterType.filterComboBoxValue uses the stable identifier the
code now expects (e.g., the enum/value used by processFilledOutSkippedFilter
such as "filledOut" or FilterComboBoxValue.FilledOut) for the openTextQ case so
getFormattedFilters(survey, selectedFilter, ...) produces { op: "filledOut" } as
asserted; adjust the selectedFilter construction in this test (and any similar
tests) to reference that stable identifier rather than the English label.

In `@apps/web/app/lib/surveys/surveys.ts`:
- Around line 538-566: processMetaFilters currently references META_OP_MAP which
is defined only inside generateElementAndFilterOptions, causing a
ReferenceError; fix by either (A) defining META_OP_MAP at module scope so
processMetaFilters can access it, or (B) add a parameter (e.g., metaOpMap) to
processMetaFilters and pass the existing META_OP_MAP from
generateElementAndFilterOptions (update all callers accordingly); ensure you
update the function signature of processMetaFilters and every call site
(including generateElementAndFilterOptions) or move the constant above the
function definition so the symbol is available at module scope.
- Around line 302-403: The helper functions (processFilledOutSkippedFilter,
processRankingFilter, processMultipleChoiceFilter, processNPSRatingFilter,
processCTAFilter, processConsentFilter) are comparing against hardcoded English
labels which breaks i18n; change the filter option shape so UI displays
translated labels but also carries a stable identifier (e.g., add fields like
optionId / valueId / comboId on conditionOptions/filterOptions and ensure
ElementFilterComboBox passes those ids into getFormattedFilters), then update
each helper to switch on these stable identifiers instead of
filterType.filterComboBoxValue or filterType.filterValue (and still parse ints
for NPS/rating when the identifier expects numeric values). Ensure UI uses the
label for display only and logic uses the stable id for comparisons.
- Around line 405-431: processPictureSelectionFilter currently parses the choice
index from the translated display string (option.split(" ")[1]) which breaks
localization; instead store the choice index or id when building the combo
options (e.g., include an explicit value/index field on each filterComboBoxValue
entry) and update processPictureSelectionFilter to read that stored index/id
(from filterType.filterComboBoxValue[*].value or similar) to map to
element.choices (use element?.choices[index]?.id or the stored choice id) so
parsing of translated labels is eliminated. Ensure the code references
processPictureSelectionFilter, filterType.filterComboBoxValue, and
getElementsFromBlocks element.

---

Duplicate comments:
In `@apps/web/app/lib/surveys/surveys.ts`:
- Around line 568-587: processQuotaFilters uses hardcoded, localized strings in
statusMap which breaks for translations; change it to map from an unlocalized
identifier (e.g., the combo value id/key) instead of the displayed text. In the
processQuotaFilters function, replace the statusMap keys that use
String(filterType.filterComboBoxValue) with the stable identifier field on the
filter combo value (for example filterType.filterComboBoxValueId or
filterType.filterComboBoxValue.key), update the lookup to use that id (e.g.,
const op = statusMap[filterType.filterComboBoxValueId]) and ensure you keep a
safe fallback (no-op) if the id is missing; this preserves the existing behavior
while removing reliance on translated strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e2dbdf7-c4c6-41ac-ad4f-fd1667d27bf5

📥 Commits

Reviewing files that changed from the base of the PR and between b5eaa4c and 6e35465.

⛔ Files ignored due to path filters (14)
  • apps/web/locales/de-DE.json is excluded by !apps/web/locales/**
  • apps/web/locales/en-US.json is excluded by !apps/web/locales/**
  • apps/web/locales/es-ES.json is excluded by !apps/web/locales/**
  • apps/web/locales/fr-FR.json is excluded by !apps/web/locales/**
  • apps/web/locales/hu-HU.json is excluded by !apps/web/locales/**
  • apps/web/locales/ja-JP.json is excluded by !apps/web/locales/**
  • apps/web/locales/nl-NL.json is excluded by !apps/web/locales/**
  • apps/web/locales/pt-BR.json is excluded by !apps/web/locales/**
  • apps/web/locales/pt-PT.json is excluded by !apps/web/locales/**
  • apps/web/locales/ro-RO.json is excluded by !apps/web/locales/**
  • apps/web/locales/ru-RU.json is excluded by !apps/web/locales/**
  • apps/web/locales/sv-SE.json is excluded by !apps/web/locales/**
  • apps/web/locales/zh-Hans-CN.json is excluded by !apps/web/locales/**
  • apps/web/locales/zh-Hant-TW.json is excluded by !apps/web/locales/**
📒 Files selected for processing (3)
  • apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResponseFilter.tsx
  • apps/web/app/lib/surveys/surveys.test.ts
  • apps/web/app/lib/surveys/surveys.ts

@jobenjada
Copy link
Copy Markdown
Member

@Dhruwang not relevant for 4.9, lets get this wrapped up for 5.0 :)

@Dhruwang
Copy link
Copy Markdown
Member

Hey @gulshank0, thanks for working on this! Unfortunately, we need to close this PR due to a fundamental design issue.

The problem: The current approach uses translated strings as both display labels and machine-comparable values. This means filtering breaks in every non-English locale.

For example, the filter options now produce translated values like "Gleich" (German for "Equals"), but the downstream processing in getFormattedFilters and all its helper functions (processFilledOutSkippedFilter, processNPSRatingFilter, processCTAFilter, processEqualsNotEqualsFilter, processMetaFilters, processQuotaFilters, etc.) still compare against hardcoded English strings like "Equals", "Filled out", "Clicked", etc. None of those comparisons will match in non-English locales, so filters silently do nothing.

There's also a runtime bug where META_OP_MAP was converted from an object to a function but Object.keys(META_OP_MAP) on line 230 was not updated to Object.keys(META_OP_MAP(t)).

The correct approach would be to separate filter values (locale-independent keys used for logic) from display labels (translated text shown in the UI). For example:

type FilterOption = { value: string; label: string };

// value stays constant, label gets translated
{ value: "equals", label: t("environments.surveys.filter.equals") }

This way the comparison logic in getFormattedFilters continues matching against stable keys, and the UI displays the translated labels. This is a larger structural change that touches both the option-generation and filter-processing sides.

We appreciate the effort and the thorough translations across all 14 locales! If you'd like to take another crack at it with the value/label separation approach, we'd be happy to review a new PR.

@Dhruwang Dhruwang closed this Apr 21, 2026
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.

Filter criteria are not marked as translatable

3 participants