From 169cbc1bcb909b1a7d1d59cb10b9d1b56f139f57 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Thu, 29 Jan 2026 05:00:43 +0000
Subject: [PATCH] Optimize LanguageProvider context value memoization
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
---
src/i18n/__tests__/perf.test.tsx | 50 ++++++++++++++++++++++++++++++++
src/i18n/index.tsx | 10 +++----
2 files changed, 55 insertions(+), 5 deletions(-)
create mode 100644 src/i18n/__tests__/perf.test.tsx
diff --git a/src/i18n/__tests__/perf.test.tsx b/src/i18n/__tests__/perf.test.tsx
new file mode 100644
index 0000000..e16433b
--- /dev/null
+++ b/src/i18n/__tests__/perf.test.tsx
@@ -0,0 +1,50 @@
+// @vitest-environment jsdom
+import React, { useState } from 'react';
+import { render, screen, act } from '@testing-library/react';
+import { LanguageProvider, useTranslation } from '../index';
+import { describe, it, expect } from 'vitest';
+
+describe('LanguageProvider Performance', () => {
+ it('should prevent consumers from re-rendering when provider re-renders but value is unchanged', async () => {
+ let renderCount = 0;
+
+ const Consumer = () => {
+ useTranslation();
+ renderCount++;
+ return
Consumer
;
+ };
+
+ const MemoizedConsumer = React.memo(Consumer);
+
+ const Wrapper = () => {
+ const [count, setCount] = useState(0);
+ return (
+
+
+
{count}
+
+
+
+
+ );
+ };
+
+ render();
+
+ // Wait for effects to settle (language detection might trigger update)
+ await act(async () => {
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
+
+ const initialRenderCount = renderCount;
+
+ // Force re-render of Wrapper, which causes re-render of LanguageProvider
+ const button = screen.getByText('Force Render');
+ await act(async () => {
+ button.click();
+ });
+
+ // After optimization, renderCount should not increase.
+ expect(renderCount).toBe(initialRenderCount);
+ });
+});
diff --git a/src/i18n/index.tsx b/src/i18n/index.tsx
index f51c923..7be82f7 100644
--- a/src/i18n/index.tsx
+++ b/src/i18n/index.tsx
@@ -1,4 +1,4 @@
-import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
+import { createContext, useContext, useState, useEffect, useMemo, useCallback, type ReactNode } from 'react';
import { de, type Translations } from './de';
import { en } from './en';
@@ -35,11 +35,11 @@ export function LanguageProvider({ children }: { children: ReactNode }) {
setIsInitialized(true);
}, []);
- const setLanguage = (lang: Language) => {
+ const setLanguage = useCallback((lang: Language) => {
setLanguageState(lang);
localStorage.setItem(STORAGE_KEY, lang);
document.documentElement.lang = lang;
- };
+ }, []);
useEffect(() => {
if (isInitialized) {
@@ -47,11 +47,11 @@ export function LanguageProvider({ children }: { children: ReactNode }) {
}
}, [language, isInitialized]);
- const value: LanguageContextType = {
+ const value = useMemo(() => ({
language,
setLanguage,
t: translations[language],
- };
+ }), [language, setLanguage]);
return (