From 04da737bbf4f30d7a3976d4dccf45f73d7d1018f Mon Sep 17 00:00:00 2001 From: Deveshi Dwivedi Date: Thu, 22 May 2025 10:53:56 +0530 Subject: [PATCH 1/2] feat: detect and use browser language as default for first-time users --- client/i18n.js | 19 +++- client/modules/IDE/reducers/preferences.js | 3 +- client/utils/language-utils.js | 107 +++++++++++++++++++++ 3 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 client/utils/language-utils.js diff --git a/client/i18n.js b/client/i18n.js index 722e3856a5..25ce8f19d9 100644 --- a/client/i18n.js +++ b/client/i18n.js @@ -21,6 +21,8 @@ import { enIN } from 'date-fns/locale'; +import getPreferredLanguage from './utils/language-utils'; + const fallbackLng = ['en-US']; export const availableLanguages = [ @@ -42,6 +44,21 @@ export const availableLanguages = [ 'ur' ]; +const detectedLanguage = getPreferredLanguage( + availableLanguages, + fallbackLng[0] +); + +let initialLanguage = detectedLanguage; + +// if user has a saved preference (e.g., from redux or window.__INITIAL_STATE__), use that +if ( + window.__INITIAL_STATE__?.preferences?.language && + availableLanguages.includes(window.__INITIAL_STATE__.preferences.language) +) { + initialLanguage = window.__INITIAL_STATE__.preferences.language; +} + export function languageKeyToLabel(lang) { const languageMap = { be: 'বাংলা', @@ -104,7 +121,7 @@ i18n // .use(LanguageDetector)// to detect the language from currentBrowser .use(Backend) // to fetch the data from server .init({ - lng: 'en-US', + lng: initialLanguage, fallbackLng, // if user computer language is not on the list of available languages, than we will be using the fallback language specified earlier debug: false, backend: options, diff --git a/client/modules/IDE/reducers/preferences.js b/client/modules/IDE/reducers/preferences.js index 630fa465ef..d6323c4fd2 100644 --- a/client/modules/IDE/reducers/preferences.js +++ b/client/modules/IDE/reducers/preferences.js @@ -1,4 +1,5 @@ import * as ActionTypes from '../../../constants'; +import i18n from '../../../i18n'; export const initialState = { tabIndex: 0, @@ -11,7 +12,7 @@ export const initialState = { gridOutput: false, theme: 'light', autorefresh: false, - language: 'en-US', + language: i18n.language, autocloseBracketsQuotes: true, autocompleteHinter: false }; diff --git a/client/utils/language-utils.js b/client/utils/language-utils.js new file mode 100644 index 0000000000..306cabbc3e --- /dev/null +++ b/client/utils/language-utils.js @@ -0,0 +1,107 @@ +/** + * Utility functions for language detection and handling + */ + +function detectLanguageFromUserAgent(userAgent) { + const langRegexes = [ + /\b([a-z]{2}(-[A-Z]{2})?);/i, // matches patterns like "en;" or "en-US;" + /\[([a-z]{2}(-[A-Z]{2})?)\]/i // matches patterns like "[en]" or "[en-US]" + ]; + + const match = langRegexes.reduce((result, regex) => { + if (result) return result; + const matches = userAgent.match(regex); + return matches && matches[1] ? matches[1] : null; + }, null); + + return match; +} + +function getPreferredLanguage(supportedLanguages = [], defaultLanguage = 'en') { + if (typeof navigator === 'undefined') { + return defaultLanguage; + } + + const normalizeLanguage = (langCode) => langCode.toLowerCase().trim(); + + const normalizedSupported = supportedLanguages.map(normalizeLanguage); + + if (navigator.languages && navigator.languages.length) { + const matchedLang = navigator.languages.find((browserLang) => { + const normalizedBrowserLang = normalizeLanguage(browserLang); + + const hasExactMatch = + normalizedSupported.findIndex( + (lang) => lang === normalizedBrowserLang + ) !== -1; + + if (hasExactMatch) { + return true; + } + + const languageOnly = normalizedBrowserLang.split('-')[0]; + const hasLanguageOnlyMatch = + normalizedSupported.findIndex( + (lang) => lang === languageOnly || lang.startsWith(`${languageOnly}-`) + ) !== -1; + + return hasLanguageOnlyMatch; + }); + + if (matchedLang) { + const normalizedMatchedLang = normalizeLanguage(matchedLang); + const exactMatchIndex = normalizedSupported.findIndex( + (lang) => lang === normalizedMatchedLang + ); + + if (exactMatchIndex !== -1) { + return supportedLanguages[exactMatchIndex]; + } + + const languageOnly = normalizedMatchedLang.split('-')[0]; + const languageOnlyMatchIndex = normalizedSupported.findIndex( + (lang) => lang === languageOnly || lang.startsWith(`${languageOnly}-`) + ); + + if (languageOnlyMatchIndex !== -1) { + return supportedLanguages[languageOnlyMatchIndex]; + } + } + } + + if (navigator.language) { + const normalizedNavLang = normalizeLanguage(navigator.language); + const exactMatchIndex = normalizedSupported.findIndex( + (lang) => lang === normalizedNavLang + ); + + if (exactMatchIndex !== -1) { + return supportedLanguages[exactMatchIndex]; + } + + const languageOnly = normalizedNavLang.split('-')[0]; + const languageOnlyMatchIndex = normalizedSupported.findIndex( + (lang) => lang === languageOnly || lang.startsWith(`${languageOnly}-`) + ); + + if (languageOnlyMatchIndex !== -1) { + return supportedLanguages[languageOnlyMatchIndex]; + } + } + + if (navigator.userAgent) { + const userAgentLang = detectLanguageFromUserAgent(navigator.userAgent); + if ( + userAgentLang && + normalizedSupported.includes(normalizeLanguage(userAgentLang)) + ) { + const index = normalizedSupported.indexOf( + normalizeLanguage(userAgentLang) + ); + return supportedLanguages[index]; + } + } + return defaultLanguage; +} + +export default getPreferredLanguage; From e5dc04dc75fd818f7013fc58d060563b10110cd2 Mon Sep 17 00:00:00 2001 From: Deveshi Dwivedi Date: Wed, 28 May 2025 12:55:35 +0530 Subject: [PATCH 2/2] refactor: remove userAgent language detection --- client/utils/language-utils.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/client/utils/language-utils.js b/client/utils/language-utils.js index 306cabbc3e..b173a3137f 100644 --- a/client/utils/language-utils.js +++ b/client/utils/language-utils.js @@ -2,21 +2,6 @@ * Utility functions for language detection and handling */ -function detectLanguageFromUserAgent(userAgent) { - const langRegexes = [ - /\b([a-z]{2}(-[A-Z]{2})?);/i, // matches patterns like "en;" or "en-US;" - /\[([a-z]{2}(-[A-Z]{2})?)\]/i // matches patterns like "[en]" or "[en-US]" - ]; - - const match = langRegexes.reduce((result, regex) => { - if (result) return result; - const matches = userAgent.match(regex); - return matches && matches[1] ? matches[1] : null; - }, null); - - return match; -} - function getPreferredLanguage(supportedLanguages = [], defaultLanguage = 'en') { if (typeof navigator === 'undefined') { return defaultLanguage; @@ -89,18 +74,6 @@ function getPreferredLanguage(supportedLanguages = [], defaultLanguage = 'en') { } } - if (navigator.userAgent) { - const userAgentLang = detectLanguageFromUserAgent(navigator.userAgent); - if ( - userAgentLang && - normalizedSupported.includes(normalizeLanguage(userAgentLang)) - ) { - const index = normalizedSupported.indexOf( - normalizeLanguage(userAgentLang) - ); - return supportedLanguages[index]; - } - } return defaultLanguage; }