Skip to content

feat: add option to change html lang#34501

Open
mehm8128 wants to merge 1 commit intostorybookjs:nextfrom
mehm8128:feat/html-document-lang-option
Open

feat: add option to change html lang#34501
mehm8128 wants to merge 1 commit intostorybookjs:nextfrom
mehm8128:feat/html-document-lang-option

Conversation

@mehm8128
Copy link
Copy Markdown
Contributor

@mehm8128 mehm8128 commented Apr 8, 2026

close #11706
related to #15541

What I did

I added the option to allow to change HTML lang attribute.
This option is essential for WCAG2.2 Success Criterion 3.1.1: Language of Page.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

  1. Run a sandbox for template, e.g. yarn task --task sandbox --start-from auto --template react-vite/default-ts
  2. add htmlLang option to main.ts of your sandbox.

for example

export default defineMain({
  ...other options,
  htmlLang: "ja"
})
  1. Open Storybook in your browser
  2. you'll confirm that the html element has the language attribute you provided: <html lang="ja">

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features
    • Configurable HTML language attribute — set a preferred lang for generated Storybook HTML (defaults to "en" if unspecified) to improve internationalization and accessibility across preview pages and templates.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

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: cbe0031c-9bd8-4de5-88be-e95a394c9a8f

📥 Commits

Reviewing files that changed from the base of the PR and between 70f90ab and 9d39a5a.

📒 Files selected for processing (10)
  • code/builders/builder-vite/input/iframe.html
  • code/builders/builder-vite/src/transform-iframe-html.ts
  • code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
  • code/builders/builder-webpack5/templates/preview.ejs
  • code/core/assets/server/template.ejs
  • code/core/src/builder-manager/index.ts
  • code/core/src/builder-manager/utils/data.ts
  • code/core/src/builder-manager/utils/template.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/types/modules/core-common.ts
✅ Files skipped from review due to trivial changes (4)
  • code/core/assets/server/template.ejs
  • code/builders/builder-vite/input/iframe.html
  • code/core/src/builder-manager/utils/data.ts
  • code/builders/builder-webpack5/templates/preview.ejs
🚧 Files skipped from review as they are similar to previous changes (4)
  • code/builders/builder-vite/src/transform-iframe-html.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
  • code/core/src/builder-manager/utils/template.ts

📝 Walkthrough

Walkthrough

The changes add support for a configurable HTML language attribute (htmlLang) that flows from presets through builder data, into builders (Vite/Webpack) and final HTML templates, replacing hardcoded lang values with a configurable value that defaults to 'en'.

Changes

Cohort / File(s) Summary
Type Definitions
code/core/src/types/modules/core-common.ts
Added optional htmlLang to StorybookConfigRaw and StorybookConfig (preset-enabled) with @default 'en'.
Preset System
code/core/src/core-server/presets/common-preset.ts
Added htmlLang preset that returns the previous value or defaults to 'en'.
Data Flow Utilities
code/core/src/builder-manager/utils/data.ts, code/core/src/builder-manager/utils/template.ts
getData() now includes htmlLang; renderHTML() signature extended to accept optional htmlLang and injects `(await htmlLang)
Builder Manager
code/core/src/builder-manager/index.ts
Starter and builder generators destructure htmlLang from getData() and pass it into renderHTML() calls.
Vite Builder
code/builders/builder-vite/input/iframe.html, code/builders/builder-vite/src/transform-iframe-html.ts
Replaced fixed lang="en" with [LANGUAGE HERE] placeholder in iframe HTML; transformer now substitutes placeholder with resolved htmlLang or 'en'.
Webpack5 Builder
code/builders/builder-webpack5/templates/preview.ejs, code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
Template lang attribute rendered from <%= htmlLang %>; webpack preview config applies htmlLang preset and passes it into HtmlWebpackPlugin template parameters.
Core Server Template
code/core/src/assets/server/template.ejs
<html> lang attribute updated to use htmlLang when defined, otherwise fallback to 'en'.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Config
    participant Presets
    participant BuilderManager
    participant Builder (Vite/Webpack)
    participant Template

    User->>Config: set htmlLang (optional)
    Config->>Presets: apply('htmlLang')
    Presets-->>BuilderManager: htmlLang value
    BuilderManager->>Builder: renderHTML(..., htmlLang)
    Builder->>Template: provide templateParameters (htmlLang)
    Template-->>User: rendered HTML with lang=(htmlLang || 'en')
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes


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.

🧹 Nitpick comments (2)
code/builders/builder-webpack5/templates/preview.ejs (1)

2-2: Consider adding a defensive fallback for consistency.

The server template (code/core/assets/server/template.ejs) uses a defensive check: typeof htmlLang !== 'undefined' ? htmlLang : 'en'. This template directly uses <%= htmlLang %> without a fallback.

While the preset system should always provide a default value of 'en', adding a consistent fallback would be more defensive:

<html lang="<%= typeof htmlLang !== 'undefined' ? htmlLang : 'en' %>">

This is a minor inconsistency and the current implementation should work correctly given the preset guarantees.

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

In `@code/builders/builder-webpack5/templates/preview.ejs` at line 2, The template
uses <%= htmlLang %> directly which lacks a defensive fallback; update the
preview.ejs HTML tag to use a typeof check on the htmlLang variable (same
pattern as server/template.ejs) so it falls back to 'en' when htmlLang is
undefined — locate the <html lang="..."> line in preview.ejs and replace the
direct <%= htmlLang %> usage with a ternary typeof htmlLang !== 'undefined' ?
htmlLang : 'en' expression.
code/core/src/builder-manager/utils/template.ts (1)

48-48: Normalize htmlLang before fallback to avoid invalid whitespace values.

(await htmlLang) || 'en' won’t catch whitespace-only inputs, so <html lang=" "> can slip through.

Proposed refactor
-    htmlLang: (await htmlLang) || 'en',
+    htmlLang: (await htmlLang)?.trim() || 'en',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/builder-manager/utils/template.ts` at line 48, The current
assignment htmlLang: (await htmlLang) || 'en' allows whitespace-only values to
pass through; update the logic around htmlLang (the awaited value used for
htmlLang) to trim and check emptiness before falling back to 'en' — i.e., await
htmlLang into a temporary (or apply .then/.trim) then if the trimmed string is
empty use 'en'; locate the usage of htmlLang in template.ts and replace the
direct fallback with a normalized-trim-and-empty-check to ensure values like "  
" do not become the HTML lang attribute.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code/builders/builder-webpack5/templates/preview.ejs`:
- Line 2: The template uses <%= htmlLang %> directly which lacks a defensive
fallback; update the preview.ejs HTML tag to use a typeof check on the htmlLang
variable (same pattern as server/template.ejs) so it falls back to 'en' when
htmlLang is undefined — locate the <html lang="..."> line in preview.ejs and
replace the direct <%= htmlLang %> usage with a ternary typeof htmlLang !==
'undefined' ? htmlLang : 'en' expression.

In `@code/core/src/builder-manager/utils/template.ts`:
- Line 48: The current assignment htmlLang: (await htmlLang) || 'en' allows
whitespace-only values to pass through; update the logic around htmlLang (the
awaited value used for htmlLang) to trim and check emptiness before falling back
to 'en' — i.e., await htmlLang into a temporary (or apply .then/.trim) then if
the trimmed string is empty use 'en'; locate the usage of htmlLang in
template.ts and replace the direct fallback with a
normalized-trim-and-empty-check to ensure values like "   " do not become the
HTML lang attribute.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86fd4ca8-f270-42a4-aefb-d3396747ecfe

📥 Commits

Reviewing files that changed from the base of the PR and between ad5c2a9 and 70f90ab.

📒 Files selected for processing (10)
  • code/builders/builder-vite/input/iframe.html
  • code/builders/builder-vite/src/transform-iframe-html.ts
  • code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts
  • code/builders/builder-webpack5/templates/preview.ejs
  • code/core/assets/server/template.ejs
  • code/core/src/builder-manager/index.ts
  • code/core/src/builder-manager/utils/data.ts
  • code/core/src/builder-manager/utils/template.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/types/modules/core-common.ts

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

Projects

Status: Empathy Queue (prioritized)

Development

Successfully merging this pull request may close these issues.

Option to change the language attribute

2 participants