tududi/frontend/i18n.ts
Chris f9b21dff0a
Fix today race condition (#75)
* Move frontend to root

* Fix backend issues

* Remove old routes

* Setup Dockerfile

* Fix today /tags multiplt requests issue

* Fix race condition on today's inbox widget

* Fix cors development issue

* Fix CORS for Dockerfile

* Fix dockerised settings for infinite loop

* Fix translation issues

* fixup! Fix translation issues

---------

Co-authored-by: Your Name <you@example.com>
2025-06-13 14:20:24 +03:00

197 lines
5.3 KiB
TypeScript

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
const isDevelopment = process.env.NODE_ENV === 'development';
const fallbackResources = {
en: {
translation: {
common: {
loading: 'Loading...',
appLoading: 'Loading application... Please wait.',
error: 'Error',
},
auth: {
login: 'Login',
register: 'Register',
},
errors: {
somethingWentWrong: 'Something went wrong, please try again',
},
},
},
};
const devResources = isDevelopment ? {
en: {
translation: fallbackResources.en.translation,
},
} : undefined;
const i18nInstance = i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next);
i18nInstance.init({
fallbackLng: 'en',
debug: isDevelopment,
load: 'languageOnly',
supportedLngs: ['en', 'es', 'el', 'jp', 'ua', 'de'],
nonExplicitSupportedLngs: true,
resources: devResources,
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
lookupQuerystring: 'lng',
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
caches: ['localStorage', 'cookie']
},
interpolation: {
escapeValue: false,
},
defaultNS: 'translation',
ns: ['translation'],
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
queryStringParams: { v: '1' },
requestOptions: {
cache: 'default',
credentials: 'same-origin',
mode: 'cors'
}
},
})
.then(() => {
const loadPath = isDevelopment ? `./locales/${i18n.language}/translation.json` : `/locales/${i18n.language}/translation.json`;
fetch(loadPath)
.then(response => {
if (!response.ok) {
if (isDevelopment) {
return fetch(`/locales/${i18n.language}/translation.json`);
}
throw new Error(`Failed to fetch translation: ${response.status}`);
}
return response.json();
})
.then(data => {
i18n.addResourceBundle(i18n.language, 'translation', data, true, true);
})
.catch(() => {
if (isDevelopment) {
try {
setTimeout(() => {
fetch(`/locales/${i18n.language}/translation.json`, {
headers: { 'Accept': 'application/json' },
mode: 'cors'
})
.then(res => res.json())
.then(data => {
i18n.addResourceBundle(i18n.language, 'translation', data, true, true);
})
.catch(() => {});
}, 1000);
} catch (e) {}
}
});
});
i18n.on('initialized', () => {});
i18n.on('loaded', () => {});
i18n.on('failedLoading', () => {});
i18n.on('missingKey', () => {});
const dispatchLanguageChangeEvent = (lng: string) => {
const event = new CustomEvent('app-language-changed', { detail: { language: lng } });
window.dispatchEvent(event);
};
i18n.on('languageChanged', (lng) => {
localStorage.setItem('i18nextLng', lng);
document.documentElement.lang = lng;
const handleTranslationsLoaded = () => {
dispatchLanguageChangeEvent(lng);
if (i18n.services && i18n.services.resourceStore) {
const currentNS = i18n.options.defaultNS || 'translation';
i18n.reloadResources(lng, currentNS);
}
};
if (!i18n.hasResourceBundle(lng, 'translation')) {
const loadPath = isDevelopment
? `./locales/${lng}/translation.json`
: `/locales/${lng}/translation.json`;
fetch(loadPath)
.then(response => {
if (!response.ok) {
return fetch(`/locales/${lng}/translation.json`);
}
return response;
})
.then(response => response.json())
.then(data => {
if (data) {
i18n.addResourceBundle(lng, 'translation', data, true, true);
handleTranslationsLoaded();
}
})
.catch(() => {
handleTranslationsLoaded();
});
} else {
handleTranslationsLoaded();
}
});
declare global {
interface WindowEventMap {
'app-language-changed': CustomEvent<{ language: string }>;
}
interface Window {
checkTranslation: (key: string) => void;
forceLanguageReload: (lng?: string) => void;
}
}
window.checkTranslation = (key: string) => {
try {
const translation = i18n.t(key);
return translation;
} catch {
return null;
}
};
window.forceLanguageReload = (lng?: string) => {
const targetLng = lng || i18n.language;
i18n.reloadResources(targetLng, 'translation')
.then(() => {
dispatchLanguageChangeEvent(targetLng);
if (i18n.services && i18n.services.resourceStore) {
Object.values(i18n.services.resourceStore.data).forEach(lang => {
if (lang.translation && typeof lang.translation === 'object' && lang.translation !== null) {
const temp = {...lang.translation as Record<string, unknown>};
lang.translation = temp;
}
});
}
if (lng) {
setTimeout(() => {
i18n.changeLanguage(targetLng);
}, 50);
}
})
.catch(() => {});
};
export default i18n;