Skip to content

✨ Theme i18n converted to use i18next (with feature flag) #23161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

cathysarisky
Copy link
Member

@cathysarisky cathysarisky commented May 4, 2025

Goal: Replace the old theme i18n implementation with use of i18next, matching what we do for the apps and ghost core.

This PR:

  • adds a themeTranslation feature flag (perhaps badly named?) that turns on using i18next.
  • adds changes necessary to ghost/i18n to accommodate theme translations
  • adds theme-engine/i18next, that parallels the theme-engine/i18n folder structure but uses i18next.
  • updates the 't' helper to check the themeTranslation flag and then use the correct t from either the i18n or i18next folder.
  • adds tests for the new implemetation

I don't love having an i18n and i18next folder, but I figure that once we're sure we like the new i18next implementation, we can nuke the logic in the t helper, the theme-engine/i18n folder, and that set of tests.

Copy link
Contributor

coderabbitai bot commented May 4, 2025

Walkthrough

This change introduces a feature-flag-driven system for theme translation using i18next in place of the existing translation package. A new beta feature flag, themeTranslation, is added to the public beta features list, and a corresponding toggle is included in the admin settings UI. The translation helper function (t) and the theme engine are updated to conditionally initialize and use either the legacy translation service or the new i18next-based service, depending on the feature flag state. The i18next implementation is encapsulated in a new ThemeI18n class, which handles loading and fallback of theme locale files. The i18n core library is updated to support a new theme namespace with custom resource loading and interpolation settings. Comprehensive unit tests are added for the new translation logic, covering translation retrieval, fallback behavior, and initialization for both the helper and the i18n library.

Suggested labels

affects:i18n, browser-tests


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45a486c and 221fb28.

📒 Files selected for processing (1)
  • ghost/i18n/test/i18n.test.js (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: Ghost-CLI tests
  • GitHub Check: Admin-X Settings tests
  • GitHub Check: Database tests (Node 20.11.1, sqlite3)
  • GitHub Check: Database tests (Node 22.13.1, mysql8)
  • GitHub Check: Unit tests (Node 22.13.1)
  • GitHub Check: Regression tests (Node 20.11.1, sqlite3)
  • GitHub Check: Unit tests (Node 20.11.1)
  • GitHub Check: Database tests (Node 20.11.1, mysql8)
  • GitHub Check: Regression tests (Node 20.11.1, mysql8)
  • GitHub Check: Lint
  • GitHub Check: i18n
🔇 Additional comments (13)
ghost/i18n/test/i18n.test.js (13)

4-4: Good addition of fs-extra for managing test files

The fs-extra library is appropriate for handling test directories and files since it provides additional utility methods beyond the native fs module, making test setup and teardown cleaner.


164-179: Well-structured test setup and teardown

The test setup creates a temporary directory for theme locale files and ensures proper cleanup after each test using fs-extra. This is a good practice that prevents test pollution and leftover files.


181-198: Good test for loading translations from theme locales

This test properly verifies that translations are loaded from the theme locales directory by creating test JSON files for English and French, then checking that the translation function returns the expected values for each language.


200-211: Good test for fallback behavior

This test verifies the important fallback functionality - when a translation is missing in the requested language, it correctly falls back to English. This ensures users always see readable content even when translations are incomplete.


213-217: Good test for handling missing translation files

This test verifies that the system gracefully handles the case when no translation files exist by falling back to using the key as the translation. This is important for robustness when themes don't provide translations.


219-226: Good error handling test for invalid JSON files

This test confirms that the system handles invalid JSON files gracefully rather than crashing. This is important for production resilience since theme files could potentially be corrupted or malformed.


228-248: Thorough configuration verification

This test thoroughly verifies that i18next is initialized with the correct configuration for theme translations, including language settings, namespaces, fallback language, and ensuring resources are loaded correctly.


250-267: Comprehensive interpolation testing

These tests verify variable interpolation in theme translations both for single variables and multiple variables, which is a critical feature for dynamic content in themes.


269-287: Good comparison of interpolation formats across namespaces

These tests clearly document and verify the different interpolation delimiter conventions used across different namespaces:

  • Theme namespace: single curly braces {name}
  • Portal namespace: double curly braces {{name}}
  • Newsletter namespace: single curly braces {name}

This explicit testing helps prevent confusion and inconsistencies.


289-296: Important test for HTML handling in interpolated values

This test verifies that HTML tags and special characters in interpolated values are not encoded. This is important for theme development where HTML might be intentionally included in translations.


299-349: Comprehensive initialization tests for all namespaces

These tests thoroughly verify the correct initialization configuration for all three namespaces (portal, theme, newsletter), including language settings, namespace configuration, and the appropriate interpolation delimiters for each. This ensures consistent behavior across different parts of the application.


351-357: Good test for Norwegian language fallback chain

This test verifies the special Norwegian language fallback chain (no → nb → en), which is an important edge case in internationalization. This ensures Norwegian users get the best possible translations.


359-364: Good edge case test for missing theme path

This test verifies that when no theme path is provided, the system initializes with empty theme resources rather than crashing. This is important for robustness in different initialization scenarios.

✨ Finishing Touches
  • 📝 Generate Docstrings

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

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@cathysarisky cathysarisky mentioned this pull request May 5, 2025
@cathysarisky cathysarisky marked this pull request as ready for review May 5, 2025 01:02
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 (6)
ghost/core/core/frontend/services/theme-engine/i18next/ThemeI18n.js (1)

64-84: Consider using async/await consistently in error handling.

While the implementation works, the nested try/catch blocks could be simplified using async/await with a more linear approach.

Consider refactoring the error handling to make it more linear:

-        try {
-            // Verify the locale file exists
-            const localePath = path.join(themeLocalesPath, `${this._locale}.json`);
-            await fs.access(localePath);
-
-            // Initialize i18n
-            this._i18n = i18nLib(this._locale, 'theme', {themePath: themeLocalesPath});
-        } catch (err) {
-            // If the requested locale fails, try English as fallback
-            try {
-                const enPath = path.join(themeLocalesPath, 'en.json');
-                await fs.access(enPath);
-                this._i18n = i18nLib('en', 'theme', {themePath: themeLocalesPath});
-            } catch (enErr) {
-                // If both fail, use the key as the translation
-                this._i18n = {
-                    t: key => key
-                };
-            }
-        }
+        // Try the requested locale first
+        const localePath = path.join(themeLocalesPath, `${this._locale}.json`);
+        const localeExists = await fs.pathExists(localePath);
+        
+        if (localeExists) {
+            this._i18n = i18nLib(this._locale, 'theme', {themePath: themeLocalesPath});
+            return;
+        }
+        
+        // If the requested locale doesn't exist, try English as fallback
+        const enPath = path.join(themeLocalesPath, 'en.json');
+        const enExists = await fs.pathExists(enPath);
+        
+        if (enExists) {
+            this._i18n = i18nLib('en', 'theme', {themePath: themeLocalesPath});
+            return;
+        }
+        
+        // If both fail, use the key as the translation
+        this._i18n = {
+            t: key => key
+        };
ghost/i18n/lib/i18n.js (2)

96-107: Interpolation delimiter logic looks good, but maintain newsletter parity
The switch to single–brace interpolation for both theme and newsletter namespaces is correct. However, only the theme branch later disables HTML escaping (escapeValue = false). If newsletter strings are also expected to contain raw HTML (previous implementation did so), you may want to disable escaping there as well––otherwise delimiter behaviour between the two namespaces is no longer symmetrical.


120-151: Synchronous disk I/O on every i18n initialisation can block the event loop
fs.readFileSync is executed each time i18n('…','theme') is called. In production this may run for every request that triggers a new Handlebars render, causing synchronous blocking.

Consider:

  1. Reading files asynchronously during theme activation and passing cached resources here.
  2. Memoising resources per themePath + lng pair so subsequent calls hit memory, not disk.

Both approaches remove disk I/O from the hot path.

ghost/i18n/test/i18n.test.js (3)

43-52: Typo in test title
it('is uses default export if available' …) reads awkwardly. Changing to “uses default export if available” improves clarity when the test output is displayed.

-it('is uses default export if available', async function () {
+it('uses default export if available', async function () {

163-299: Great coverage, but consider deduplicating helper code
The theme-related tests repeat setup logic for creating temporary locale files, writing JSON, and instantiating i18n. Extracting a small helper (e.g., createTempThemeLocales(files: Record<lng, object>)) would shorten each test and make intent clearer.

This is purely a readability/maintainability improvement—the tests are functionally solid.


250-277: Redundant interpolation tests
it('handles interpolation correctly') (250-258) and it('interpolates variables in theme translations') (260-277) overlap. Unless you intend to test distinct code paths, one consolidated test is enough and will speed up the suite slightly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc0c405 and 1b22ed5.

⛔ Files ignored due to path filters (1)
  • ghost/core/test/e2e-api/admin/__snapshots__/config.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (11)
  • apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx (1 hunks)
  • ghost/core/core/frontend/helpers/t.js (2 hunks)
  • ghost/core/core/frontend/services/handlebars.js (1 hunks)
  • ghost/core/core/frontend/services/theme-engine/active.js (2 hunks)
  • ghost/core/core/frontend/services/theme-engine/i18next/ThemeI18n.js (1 hunks)
  • ghost/core/core/frontend/services/theme-engine/i18next/index.js (1 hunks)
  • ghost/core/core/shared/labs.js (1 hunks)
  • ghost/core/test/unit/frontend/helpers/t-new.test.js (1 hunks)
  • ghost/core/test/unit/frontend/services/theme-engine/i18next.test.js (1 hunks)
  • ghost/i18n/lib/i18n.js (4 hunks)
  • ghost/i18n/test/i18n.test.js (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
ghost/core/core/frontend/services/handlebars.js (1)
ghost/core/core/frontend/helpers/t.js (2)
  • require (13-13)
  • require (14-14)
ghost/core/core/frontend/services/theme-engine/i18next/index.js (1)
ghost/core/core/frontend/helpers/t.js (3)
  • config (16-16)
  • require (13-13)
  • require (14-14)
ghost/core/test/unit/frontend/helpers/t-new.test.js (2)
ghost/core/core/frontend/helpers/t.js (3)
  • require (13-13)
  • require (14-14)
  • labs (15-15)
ghost/core/core/frontend/services/theme-engine/i18next/ThemeI18n.js (1)
  • path (3-3)
🔇 Additional comments (26)
ghost/core/core/shared/labs.js (1)

37-38: Good addition of the feature flag for theme translation

The feature flag themeTranslation is properly added to the PUBLIC_BETA_FEATURES array, making it available for users to enable or disable in the admin UI.

ghost/core/core/frontend/services/handlebars.js (1)

21-22: Good export of the new theme translation service

The new themeI18next service is appropriately exported alongside the existing themeI18n service, which aligns with the feature flag approach being implemented for theme translation.

apps/admin-x-settings/src/components/settings/advanced/labs/BetaFeatures.tsx (1)

39-42: Good addition of the theme translation toggle in labs settings

The UI toggle for the "Updated theme Translation (beta)" feature is well implemented, following the same pattern as other feature toggles. The description appropriately explains the purpose of the feature flag.

ghost/core/core/frontend/services/theme-engine/i18next/index.js (1)

1-6: Good implementation of the singleton pattern for ThemeI18n

The module correctly exports a singleton instance of ThemeI18n initialized with the appropriate base path from configuration. The ThemeI18n class is also re-exported, which provides flexibility for testing or advanced usage scenarios.

ghost/core/core/frontend/services/theme-engine/active.js (2)

21-22: Good addition of required imports

The new imports for themeI18next and labs are correctly added to support the feature flag implementation.


106-113: Good implementation of feature flag conditional logic

The conditional initialization of either the new themeI18next service or the legacy themeI18n service based on the feature flag status is well implemented. This allows for a smooth transition between implementations as users opt into the beta feature.

ghost/core/core/frontend/helpers/t.js (2)

13-17: Dependencies are correctly managed.

Good job on adding the necessary imports for the feature flag implementation. The code correctly imports themeI18next, labs, config, and settingsCache to support the conditional translation logic.


33-57: Feature flag implementation looks good.

The implementation correctly uses the themeTranslation feature flag to conditionally choose between translation systems. I like how both paths have similar initialization patterns, making the code easy to understand and maintain.

A few observations:

  1. Each translation service is initialized only when needed (lines 37-42 and 49-54)
  2. Both services receive the same configuration parameters
  3. Clear comments explain the purpose of each branch
ghost/core/test/unit/frontend/services/theme-engine/i18next.test.js (7)

1-5: Test setup imports look good.

The test file properly imports the necessary dependencies, including the ThemeI18n class that's being tested.


6-17: Test setup is well-structured.

Good job on the test setup, including:

  1. Clear description of what's being tested
  2. Proper initialization of the ThemeI18n instance with a test base path
  3. Cleaning up sinon stubs after each test

18-25: Default locale tests are appropriate.

These tests correctly verify the default locale behavior and the ability to set a different locale.


27-32: Translation initialization test looks good.

This test properly verifies that translations are loaded and can be retrieved after initialization.


33-43: Fallback behavior tests are comprehensive.

These tests verify two important fallback scenarios:

  1. Falling back to English when a translation is missing in the current locale
  2. Using the key itself as a fallback when no translation files exist

This ensures robustness of the translation system even when translations are incomplete.


45-55: Edge case handling tests are important.

Good job on testing edge cases:

  1. Handling empty keys
  2. Ensuring proper error messages when the service is used before initialization

These tests help prevent subtle bugs in production.


57-62: Key fallback test is appropriate.

This test verifies that unknown translation keys are returned as-is, which is the expected fallback behavior.

ghost/core/test/unit/frontend/helpers/t-new.test.js (6)

1-7: Imports are correctly set up.

The test file properly imports all necessary dependencies, including the helper function being tested and the feature flag module.


8-24: Test setup is comprehensive.

Good test setup that:

  1. Stubs the feature flag to ensure the new translation system is used
  2. Sets up a test path for theme fixtures
  3. Properly cleans up after tests
  4. Resets the i18n instance before each test to ensure test isolation

26-44: Locale-specific tests are thorough.

These tests properly verify translation behavior for different locales (DE and EN), ensuring the translation helper works correctly with locale-specific content.


46-64: Fallback behavior tests are important.

Good job testing fallback behavior:

  1. Falling back to English when a French translation is missing
  2. Using the key itself as fallback when no theme files exist

This ensures the translation system is robust even with incomplete translations.


66-80: Edge case handling is comprehensive.

These tests properly verify handling of edge cases:

  1. Empty translation keys
  2. Missing translation keys

Both cases correctly return an empty string as expected.


82-88: Options handling test is important.

This test verifies that the helper works even when no options are passed, which is an important edge case to support.

ghost/core/core/frontend/services/theme-engine/i18next/ThemeI18n.js (4)

1-5: Dependencies are properly imported.

The code imports all necessary dependencies for translation, file system operations, and error handling.


6-31: Class structure and initialization are well-designed.

The ThemeI18n class is well-structured with:

  1. Clear JSDoc comments explaining the parameters
  2. Proper validation of required parameters
  3. Sensible defaults (e.g., 'en' for locale)
  4. Getters and setters for testing purposes

33-85: Initialization method is robust with good fallback logic.

The init method:

  1. Validates required parameters
  2. Sets up the locale and active theme
  3. Checks if the theme has a locales directory
  4. Attempts to load the requested locale file
  5. Falls back to English if the requested locale is missing
  6. Falls back to using the key as translation if no locale files exist

This multi-level fallback approach ensures translation works even with incomplete or missing translations.


87-101: Translation method is properly implemented.

The t method:

  1. Checks if initialization has been performed
  2. Throws a clear error message if used before initialization
  3. Delegates to the i18n instance for translation
  4. Ensures the result is always a string, even if the underlying library returns something else
ghost/i18n/lib/i18n.js (1)

110-113: Potential XSS surface when escapeValue is disabled
Turning off escapeValue for the theme namespace means any HTML contained in translation files is rendered verbatim in templates. This is intentional for rich-text themes, but also opens the door for XSS if a theme bundles un-escaped user-supplied strings. Consider documenting this clearly and, if feasible, validating translation JSON during theme upload.

Comment on lines +114 to +151
let resources;
if (ns !== 'theme') {
resources = generateResources(SUPPORTED_LOCALES, ns);
} else {
// For theme namespace, we need to load translations from the theme's locales directory
resources = {};
const themeLocalesPath = options.themePath;

if (themeLocalesPath) {
// Try to load the requested locale first
try {
const localePath = path.join(themeLocalesPath, `${lng}.json`);
const content = fs.readFileSync(localePath, 'utf8');
resources[lng] = {
theme: JSON.parse(content)
};
} catch (err) {
// If the requested locale fails, try English as fallback
try {
const enPath = path.join(themeLocalesPath, 'en.json');
const content = fs.readFileSync(enPath, 'utf8');
resources[lng] = {
theme: JSON.parse(content)
};
} catch (enErr) {
// If both fail, use an empty object
resources[lng] = {
theme: {}
};
}
}
} else {
// If no theme path provided, use empty translations
resources[lng] = {
theme: {}
};
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Theme namespace does not load true fallback resources – breaks partial translations
Only the requested locale is loaded into resources, and when that file is missing you copy the English file into the requested locale key.
This means that if fr.json exists but is incomplete, i18next cannot fall back to en for the missing keys, because resources.en is never registered. Users will instead see the key string.

A minimal fix is to always load English resources separately and attach them under the en key while still loading the requested locale (if present). Example patch:

-        resources = {};
+        const loadLocaleJson = (locale) => {
+            try {
+                return JSON.parse(fs.readFileSync(path.join(themeLocalesPath, `${locale}.json`), 'utf8'));
+            } catch {
+                return {};
+            }
+        };
+
+        resources = {
+            en: {theme: loadLocaleJson('en')}
+        };
+
+        // merge requested locale on top of English so i18next can still fall back
+        resources[lng] = {
+            theme: {
+                ...resources.en.theme,
+                ...loadLocaleJson(lng)
+            }
+        };

With this change the normal fallbackLng chain works and missing keys gracefully degrade to English without duplicating data.

Committable suggestion skipped: line range outside the PR's diff.

@cathysarisky cathysarisky changed the title WIP - feature flag for theme translation updates ✨Theme i18n converted to use i18next (with feature flag) May 5, 2025
@cathysarisky cathysarisky changed the title ✨Theme i18n converted to use i18next (with feature flag) ✨ Theme i18n converted to use i18next (with feature flag) May 5, 2025
@cathysarisky
Copy link
Member Author

@coderabbitai, you wrote:
"120-151: Synchronous disk I/O on every i18n initialisation can block the event loop
fs.readFileSync is executed each time i18n('…','theme') is called. In production this may run for every request that triggers a new Handlebars render, causing synchronous blocking."

This initialization block only triggers on boot, theme change, and language change. It does /not/ trigger on page load or use of the t helper. (In addition to checking the docs, I also manually confirmed this by adding logging in that block.)

Copy link
Contributor

coderabbitai bot commented May 6, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's look at the t helper file to understand the initialization logic
cat ghost/core/core/frontend/helpers/t.js

# Then let's look at the service that initializes the translations
cat ghost/core/core/frontend/services/theme-engine/i18next/index.js
cat ghost/core/core/frontend/services/theme-engine/i18next/ThemeI18n.js

# Let's also check the active theme service that calls init
cat ghost/core/core/frontend/services/theme-engine/active.js

# Let's look at the handlebars service to see how themeI18next is exposed
cat ghost/core/core/frontend/services/handlebars.js

Length of output: 11799


@cathysarisky You're absolutely right. After examining the code in detail, I can confirm that the initialization only happens during boot, theme changes, and language changes - not on every page load or helper call.

Looking at the implementation in ghost/core/core/frontend/helpers/t.js, I can see that the initialization check is a safeguard:

// Only initializes if not already initialized
if (!themeI18next._i18n) {
    themeI18next.init({
        activeTheme: settingsCache.get('active_theme'),
        locale: config.get('locale')
    });
}

The theme's translations are loaded during the ActiveTheme constructor in theme-engine/active.js, and the initialization in the helper is just making sure the service is ready before translating. Once initialized, subsequent calls to the helper will use the already loaded translations.

Thank you for the correction and for verifying it with logging. I'll make sure to keep this in mind for future reviews.


✏️ Learnings added
Learnt from: cathysarisky
URL: https://github.com/TryGhost/Ghost/pull/23161

Timestamp: 2025-05-06T00:47:48.446Z
Learning: The theme i18n initialization in Ghost only happens during server boot, theme changes, and language changes - not on every page load or `t` helper call. The initialization check in the `t` helper is a safeguard that doesn't execute during normal page rendering once the theme is loaded.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

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

🧹 Nitpick comments (1)
ghost/i18n/test/i18n.test.js (1)

289-296: Fix string quote style issue

There's an inconsistency in the string quote style that should be fixed to maintain code consistency.

-            assert.equal(t('Welcome, {name}', {name: "<b>John O'Nolan</b>"}), 'Welcome, <b>John O\'Nolan</b>');
+            assert.equal(t('Welcome, {name}', {name: '<b>John O\'Nolan</b>'}), 'Welcome, <b>John O\'Nolan</b>');
🧰 Tools
🪛 ESLint

[error] 295-295: Strings must use singlequote.

(quotes)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b22ed5 and 45a486c.

📒 Files selected for processing (2)
  • ghost/core/core/frontend/services/theme-engine/i18next/ThemeI18n.js (1 hunks)
  • ghost/i18n/test/i18n.test.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/core/frontend/services/theme-engine/i18next/ThemeI18n.js
🧰 Additional context used
🪛 ESLint
ghost/i18n/test/i18n.test.js

[error] 295-295: Strings must use singlequote.

(quotes)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Setup
🔇 Additional comments (14)
ghost/i18n/test/i18n.test.js (14)

4-4: Well-structured addition of fsExtra dependency

The inclusion of fs-extra provides a cleaner API for file system operations, which is appropriate for creating and managing test directories and files.


163-179: Good test setup with proper cleanup

The test setup for theme resources creates isolated testing environments with a temporary directory that gets properly cleaned up after each test. This prevents test pollution and ensures reliable test execution.


181-198: Comprehensive translation loading test

This test properly validates that translations load correctly from the theme locales directory, which is a core functionality of the new i18next implementation.


200-211: Robust fallback mechanism testing

The fallback mechanism test ensures that the system gracefully falls back to English when translations are missing in the requested language, which is crucial for user experience.


213-217: Good empty case handling

Testing the behavior when no translation files exist ensures the system remains stable in edge cases and provides sensible defaults.


219-226: Excellent error handling verification

Testing how the system responds to invalid JSON files is important for ensuring robustness against corrupted translation files. This test helps prevent runtime errors for users.


228-248: Thorough i18next configuration validation

This test comprehensively verifies all aspects of the i18next configuration, ensuring the instance is initialized with the correct settings for theme translations.


250-267: Excellent interpolation testing

The test thoroughly validates variable interpolation in theme translations, covering both simple cases and multiple variables.


269-287: Good namespace-specific interpolation validation

Testing the different interpolation formats across namespaces ensures compatibility with existing translation systems while supporting the new theme translation approach.


299-315: Thorough portal namespace initialization test

This test validates the correct initialization configuration for the portal namespace, covering essential settings like language, namespaces, and interpolation.


317-332: Well-structured theme namespace initialization test

The test ensures theme namespace is correctly configured with appropriate settings, particularly the single curly brace interpolation format which differs from portal.


334-349: Complete newsletter namespace initialization test

This test properly validates the newsletter namespace configuration, maintaining consistency with the rest of the test suite.


351-357: Appropriate fallback language chain test

Testing the Norwegian language fallback chain ensures multi-language support works correctly across regional variants, which is a key aspect of internationalization.


359-364: Good edge case handling for empty theme path

Testing the case when no theme path is provided ensures the system degrades gracefully rather than throwing errors.

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.

1 participant