Skip to content

feat: allow customization of HTML lang attribute #41593

Open
salevine wants to merge 3 commits intoreleasefrom
feat/6642/enable-lang-tag
Open

feat: allow customization of HTML lang attribute #41593
salevine wants to merge 3 commits intoreleasefrom
feat/6642/enable-lang-tag

Conversation

@salevine
Copy link
Contributor

@salevine salevine commented Mar 6, 2026

Users building apps in non-English languages (e.g. German) experience corrupted UI text because browsers and translation extensions see and auto-translate page content — sometimes German-to-German, sometimes misinterpreting English UI words like "Content" as the French adjective (translated to "Thrilled").
This PR fixes the problem at three layers:
Immediate defence: Adds translate="no" and to the app shell HTML, editor UI containers (PropertyPane tabs, App Settings), and widget iframe templates. This stops browsers from offering or performing auto-translation.
Per-app language setting: Adds an "HTML Language" field to App Settings > General. App builders can enter a BCP 47 language code (e.g. de, fr, ja) that gets persisted as htmlLang on ApplicationDetail and applied to the published app's attribute at runtime via react-helmet.
Instance-level default: Adds APPSMITH_DEFAULT_HTML_LANG environment variable for self-hosted admins who want all apps on their instance to default to a specific language. Falls back gracefully to "en" when the variable is not set.

Description

Tip

Add a TL;DR when the description is longer than 500 words or extremely technical (helps the content, marketing, and DevRel team).

Please also include relevant motivation and context. List any dependencies that are required for this change. Add links to Notion, Figma or any other documents that might be relevant to the PR.

Fixes #6642

Automation

/ok-to-test tags="@tag.All"

🔍 Cypress test results

Tip

🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/23129680993
Commit: 535615e
Cypress dashboard.
Tags: @tag.All
Spec:


Mon, 16 Mar 2026 07:01:57 UTC

Communication

Should the DevRel and Marketing teams inform users about this change?

  • Yes
  • No

Summary by CodeRabbit

  • New Features

    • Added an "HTML language" setting (BCP 47 codes) in General Settings to set the app's lang attribute.
    • Published apps now respect the configured HTML language and include it in page metadata.
  • Behavior

    • App pages, loading screens, and embedded widgets opt out of automatic translation to prevent unwanted machine translation.

…d browser translations

Add per-app htmlLang setting in App Settings > General that sets the
<html lang=""> attribute on published apps via react-helmet. Includes
instance-level APPSMITH_DEFAULT_HTML_LANG env var with graceful fallback
to "en" when unset. Adds notranslate directives and translate="no" to
the app shell, editor UI containers, and widget iframe templates to
prevent browsers and extensions from auto-translating content.

Closes: appsmithorg/appsmith-ee#6642
Made-with: Cursor
@salevine salevine added the ok-to-test Required label for CI label Mar 6, 2026
@salevine salevine requested review from a team and sharat87 as code owners March 6, 2026 02:05
@salevine salevine requested review from vivek-appsmith and removed request for a team March 6, 2026 02:05
@github-actions github-actions bot added the Enhancement New feature or request label Mar 6, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Walkthrough

This change adds HTML language support and translation suppression: a new htmlLang field flows through configs, APIs, types, and UI settings; templates and generated HTML include translate="no" and Google notranslate directives; AppViewer applies the lang attribute when rendering.

Changes

Cohort / File(s) Summary
Translation Suppression
app/client/public/404.html, app/client/public/index.html, app/client/src/pages/AppIDE/components/AppSettings/AppSettings.tsx, app/client/src/pages/Editor/PropertyPane/PropertyPaneTab.tsx, app/client/src/widgets/CustomWidget/component/index.tsx, app/client/src/widgets/ExternalWidget/component/index.tsx, app/client/src/widgets/wds/WDSCustomWidget/component/createHtmlTemplate.ts, deploy/docker/fs/opt/appsmith/templates/loading.html
Added translate="no" to HTML roots and meta name="google" content="notranslate" where applicable to opt out of automatic translation.
Configuration Infrastructure
app/client/public/index.html, app/client/src/ce/configs/index.ts, app/client/src/ce/configs/types.ts, deploy/docker/fs/opt/appsmith/caddy-reconfigure.mjs
Introduced defaultHtmlLang config wired from env APPSMITH_DEFAULT_HTML_LANG into injected configs and public feature configs; deployment script exposes the env var (defaults to "en" in caddy reconfigure).
API & Type Definitions
app/client/src/ce/api/ApplicationApi.tsx, app/client/src/entities/Application/types.ts, app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/ApplicationDetailCE.java
Added optional htmlLang field to application payload/type on frontend and to ApplicationDetailCE on server (JSON view exposure).
Settings & Messages
app/client/src/ce/constants/messages.ts, app/client/src/pages/AppIDE/components/AppSettings/components/GeneralSettings.tsx
Added UI input, state, save logic for HTML language in General Settings plus label and tooltip message strings.
App Viewer
app/client/src/pages/AppViewer/AppViewerHtmlTitle.tsx, app/client/src/pages/AppViewer/index.tsx
HtmlTitle component accepts optional lang prop and sets Helmet's htmlAttributes.lang, sourced from applicationDetail.htmlLang or the default config fallback.

Sequence Diagram

sequenceDiagram
    actor User
    participant Settings as GeneralSettings UI
    participant FrontendAPI as ApplicationApi (client)
    participant Server as Appsmith Server (ApplicationDetail)
    participant Config as Appsmith Configs
    participant Viewer as AppViewer/Helmet
    participant Browser as Browser

    User->>Settings: Enter/save HTML language code
    Settings->>FrontendAPI: updateApplication(htmlLang)
    FrontendAPI->>Server: persist applicationDetail.htmlLang
    Server-->>FrontendAPI: confirmation with updated details
    FrontendAPI-->>Settings: update UI state
    Viewer->>Config: read defaultHtmlLang
    Viewer->>Server: fetch currentApplicationDetails
    Server-->>Viewer: return applicationDetail (htmlLang)
    Viewer->>Viewer: choose lang = applicationDetail.htmlLang || defaultHtmlLang || "en"
    Viewer->>Helmet: set htmlAttributes.lang
    Browser->>Browser: render <html lang="..."> and disable auto-translation (translate="no")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🌐 Set the tongue for every page today,
No stray translators in the way,
Configs, UI, server all in line,
The app speaks true in the language you define.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses HTML language customization, but linked issue #6642 requests a sidebar widget for multi-page navigation—an unrelated feature. Verify the correct issue is linked. If #6642 is incorrect, update to reference the actual issue about HTML lang attribute customization.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature: customization of the HTML lang attribute for apps.
Out of Scope Changes check ✅ Passed All changes are directly scoped to HTML lang attribute customization: meta tags, configuration wiring, UI settings, and environment variables.
Description check ✅ Passed PR description comprehensively addresses all template sections with clear motivation, implementation details across three layers, and test results.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/6642/enable-lang-tag
📝 Coding Plan
  • Generate coding plan for human review comments

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

🧹 Nitpick comments (1)
app/client/src/pages/AppIDE/components/AppSettings/components/GeneralSettings.tsx (1)

498-517: Consider adding basic BCP 47 validation.

The input accepts any free-form text, but the PR objective states it "accepts BCP 47 language codes." Invalid codes won't break functionality but won't help accessibility either. A simple regex check (e.g., /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$/) with an error message would improve UX.

💡 Optional: Add basic validation
+ const BCP47_REGEX = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$/;
+ const [isHtmlLangValid, setIsHtmlLangValid] = useState(true);

+ const validateHtmlLang = (value: string) => {
+   if (!value.trim()) return true; // Empty is valid (falls back to default)
+   return BCP47_REGEX.test(value.trim());
+ };

  <Input
    id="t--general-settings-app-language"
+   isValid={isHtmlLangValid}
+   errorMessage={isHtmlLangValid ? undefined : "Invalid language code (e.g., en, de, fr-CA)"}
    label={createMessage(GENERAL_SETTINGS_APP_LANGUAGE_LABEL)}
-   onBlur={() => saveHtmlLang(htmlLang)}
-   onChange={(value: string) => setHtmlLang(value)}
+   onBlur={() => {
+     if (validateHtmlLang(htmlLang)) saveHtmlLang(htmlLang);
+   }}
+   onChange={(value: string) => {
+     setHtmlLang(value);
+     setIsHtmlLangValid(validateHtmlLang(value));
+   }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/client/src/pages/AppIDE/components/AppSettings/components/GeneralSettings.tsx`
around lines 498 - 517, The language input currently accepts any text; add basic
BCP 47 validation in the GeneralSettings component by validating htmlLang
against a regex (e.g., /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$/) inside the
onChange/onBlur/onKeyPress handlers for the Input used for htmlLang, set a local
validation error state (e.g., htmlLangError) and display a small error Text
beneath the Input using createMessage(GENERAL_SETTINGS_APP_LANGUAGE_TOOLTIP)
style; prevent calling saveHtmlLang(htmlLang) when the value is invalid and
ensure the Input shows the error state so users get immediate feedback.
🤖 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/client/public/index.html`:
- Around line 2-5: The root <html> element currently hardcodes lang="en" which
delays language configuration until client-side boot; update the server-rendered
template so the root <html> element uses the APPSMITH_DEFAULT_HTML_LANG
environment value with a server-side fallback to "en" (the same source used
later in the client script), ensuring the initial HTML response advertises the
configured language; make the change where the root <html> tag is defined in
index.html and keep the existing client-side assignment (lines ~271-272) as-is
to remain consistent.

---

Nitpick comments:
In
`@app/client/src/pages/AppIDE/components/AppSettings/components/GeneralSettings.tsx`:
- Around line 498-517: The language input currently accepts any text; add basic
BCP 47 validation in the GeneralSettings component by validating htmlLang
against a regex (e.g., /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$/) inside the
onChange/onBlur/onKeyPress handlers for the Input used for htmlLang, set a local
validation error state (e.g., htmlLangError) and display a small error Text
beneath the Input using createMessage(GENERAL_SETTINGS_APP_LANGUAGE_TOOLTIP)
style; prevent calling saveHtmlLang(htmlLang) when the value is invalid and
ensure the Input shows the error state so users get immediate feedback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 632ba231-4d4c-4bee-abb4-ea8c444d719a

📥 Commits

Reviewing files that changed from the base of the PR and between d18802b and 6f24fce.

📒 Files selected for processing (18)
  • app/client/public/404.html
  • app/client/public/index.html
  • app/client/src/ce/api/ApplicationApi.tsx
  • app/client/src/ce/configs/index.ts
  • app/client/src/ce/configs/types.ts
  • app/client/src/ce/constants/messages.ts
  • app/client/src/entities/Application/types.ts
  • app/client/src/pages/AppIDE/components/AppSettings/AppSettings.tsx
  • app/client/src/pages/AppIDE/components/AppSettings/components/GeneralSettings.tsx
  • app/client/src/pages/AppViewer/AppViewerHtmlTitle.tsx
  • app/client/src/pages/AppViewer/index.tsx
  • app/client/src/pages/Editor/PropertyPane/PropertyPaneTab.tsx
  • app/client/src/widgets/CustomWidget/component/index.tsx
  • app/client/src/widgets/ExternalWidget/component/index.tsx
  • app/client/src/widgets/wds/WDSCustomWidget/component/createHtmlTemplate.ts
  • app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ce/ApplicationDetailCE.java
  • deploy/docker/fs/opt/appsmith/caddy-reconfigure.mjs
  • deploy/docker/fs/opt/appsmith/templates/loading.html

@salevine salevine added ok-to-test Required label for CI and removed ok-to-test Required label for CI labels Mar 6, 2026
Replace the hardcoded lang="en" with the Caddy template expression so
the initial HTML response advertises the configured language before
client-side React boots.

Made-with: Cursor
Copy link
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/client/public/index.html (1)

2-2: ⚠️ Potential issue | 🟠 Major

Keep the "en" fallback at the render site.

Line 2 and Lines 271-272 still collapse to an empty value when APPSMITH_DEFAULT_HTML_LANG is unset, so the first HTML response can ship lang="" and the client config can expose defaultHtmlLang: "". That makes the documented "en" default depend entirely on deploy-time env injection rather than the code path that renders it.

What is the correct Caddy/templates syntax to render `{{env "APPSMITH_DEFAULT_HTML_LANG"}}` with a fallback of `"en"` when the environment variable is unset?

Also applies to: 271-272

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

In `@app/client/public/index.html` at line 2, Replace the bare template calls to
{{env "APPSMITH_DEFAULT_HTML_LANG"}} in index.html with the Caddy template form
that provides a fallback value, i.e. use the env function with a default: {{env
"APPSMITH_DEFAULT_HTML_LANG" "en"}}; update the lang attribute at the top (the
html tag) and the client-config insertion that sets defaultHtmlLang (the
occurrences around lines 271-272) so they render "en" when
APPSMITH_DEFAULT_HTML_LANG is unset.
🤖 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/client/public/index.html`:
- Line 2: Replace the bare template calls to {{env
"APPSMITH_DEFAULT_HTML_LANG"}} in index.html with the Caddy template form that
provides a fallback value, i.e. use the env function with a default: {{env
"APPSMITH_DEFAULT_HTML_LANG" "en"}}; update the lang attribute at the top (the
html tag) and the client-config insertion that sets defaultHtmlLang (the
occurrences around lines 271-272) so they render "en" when
APPSMITH_DEFAULT_HTML_LANG is unset.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b39b056d-264f-46a9-94c3-d6743d19ddef

📥 Commits

Reviewing files that changed from the base of the PR and between 6f24fce and d35b79f.

📒 Files selected for processing (1)
  • app/client/public/index.html

@salevine
Copy link
Contributor Author

salevine commented Mar 8, 2026

/build-deploy-preview skip-tests=true

@github-actions
Copy link

github-actions bot commented Mar 8, 2026

Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/22827220661.
Workflow: On demand build Docker image and deploy preview.
skip-tests: true.
env: ``.
PR: 41593.
recreate: .

@github-actions
Copy link

github-actions bot commented Mar 8, 2026

Deploy-Preview-URL: https://ce-41593.dp.appsmith.com

@vivek-appsmith vivek-appsmith requested review from sondermanish and subrata71 and removed request for vivek-appsmith March 9, 2026 10:13
Copy link
Collaborator

@subrata71 subrata71 left a comment

Choose a reason for hiding this comment

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

Review comments from Claude.


app/client/public/index.html — fallback gap in the <html lang> attribute

The <html> tag on line 2 uses:

<html lang='{{env "APPSMITH_DEFAULT_HTML_LANG"}}' translate="no">

The template substitution in caddy-reconfigure.mjs replaces {{env "APPSMITH_DEFAULT_HTML_LANG"}} via:

(_, name) => (process.env[name] || extraEnv[name] || "")

Since extraEnv.APPSMITH_DEFAULT_HTML_LANG is set to process.env.APPSMITH_DEFAULT_HTML_LANG || "en", the extraEnv fallback should kick in when the env var isn't set — so in a Docker-based deployment, the rendered HTML will correctly get lang="en". That part is fine.

However, in non-Docker environments (e.g. running the client dev server directly, or cloud deployments where caddy-reconfigure isn't the one serving index.html), the Caddy template expression may not be processed at all, which would leave lang='' verbatim in the HTML, or the browser receiving the unprocessed template string. It would be safer to set an explicit fallback at the template level itself. If Caddy's template syntax supports a default, something like:

{{env "APPSMITH_DEFAULT_HTML_LANG" | default "en"}}

would make the fallback self-contained and not dependent on the extraEnv JavaScript logic.

Similarly, in the JavaScript config injection block (line ~272), parseConfig('{{env "APPSMITH_DEFAULT_HTML_LANG"}}') || "" will resolve to "" rather than "en" when the env var isn't set and the template isn't processed. The triple fallback in AppViewerHtmlTitle (lang || defaultHtmlLang || "en") does save you at runtime, but it means the config carries an empty string that has to be silently corrected downstream.


AppViewerHtmlTitle.tsx — module-level getAppsmithConfigs() call

const { defaultHtmlLang } = getAppsmithConfigs();

This is called at module load time, outside any React component or hook. If getAppsmithConfigs() reads from a mutable config store (which it does — it merges ENV_CONFIG and APPSMITH_FEATURE_CONFIGS), this value will be captured once when the module is first imported and won't reflect any subsequent config updates. For an env-var-backed config that doesn't change at runtime this is probably fine in practice, but it's inconsistent with how the rest of the codebase typically calls getAppsmithConfigs() inside the component or a hook. Worth moving it inside the component function to be consistent and future-safe.


GeneralSettings.tsx — no validation before saving

The saveHtmlLang callback does a trimmed === current guard which is good, but there's no check on whether the trimmed value is a plausible BCP 47 code before dispatching the update. A user can type something like "not a language" or paste a long string with spaces, and it will be persisted and then applied to the <html lang> attribute. Invalid lang values don't break anything today, but they degrade accessibility tool behavior and are meaningless for the feature's stated goal.

Even a lightweight check — ensuring the value matches something like /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$/ before saving, with a small inline error message — would prevent accidental invalid saves. The onBlur/Enter flow means a user can type and walk away without realizing the input was invalid.


PropertyPaneTab.tsxtranslate="no" on tabs

This adds translate="no" to the StyledTabs wrapper to prevent auto-translation of tab labels like "Content" and "Style". That's a targeted and reasonable fix for the editor. One thing worth confirming: does StyledTabs (from @appsmith/ads) forward arbitrary HTML attributes to the DOM, or could this silently get swallowed? If it's a styled-component or a custom component that doesn't spread props to the underlying DOM element, the attribute won't reach the browser and the protection won't work.


ApplicationDetailCE.java — no server-side validation

htmlLang is stored as a plain String with no length limit or format constraint. Since this flows from user input all the way into the <html lang> attribute of the served page, it's worth having at least a basic server-side sanitization (trim, max length, reject obviously invalid values) even if client-side validation is added too. Defense in depth applies here.


Overall

The feature direction is good and the code is generally clean. The main things I'd want addressed before merge are the index.html fallback gap (correctness risk for non-Docker deployments) and the missing input validation on the lang setting (both client and server side). The module-level config call in AppViewerHtmlTitle is a minor consistency issue.

@@ -1,7 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<html lang='{{env "APPSMITH_DEFAULT_HTML_LANG"}}' translate="no">
Copy link
Collaborator

Choose a reason for hiding this comment

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

The template has no inline fallback value. The caddy-reconfigure.mjs provides the "en" default only during Docker deployment. Tagging @ashit-rath to validate if this concern is not valid.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add an inline fallback value if we can. Better to be safe if the option is available

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

Labels

Enhancement New feature or request ok-to-test Required label for CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants