From 172e917664a397e8ea6c953431808373350ded9e Mon Sep 17 00:00:00 2001 From: asimonok Date: Mon, 23 Oct 2023 10:43:38 +0300 Subject: [PATCH] Setup of i18next --- package-lock.json | 63 +++++++++++++++++++++++++++++----- package.json | 2 ++ src/@types/i18next.d.ts | 8 +++++ src/constants/default.ts | 6 ++-- src/hooks/useLocalizer.ts | 16 ++++----- src/i18n/config.ts | 24 +++++++++++++ src/i18n/constants.ts | 9 +++++ src/i18n/index.ts | 3 ++ src/i18n/translations/en.json | 1 + src/i18n/translations/index.ts | 16 +++++++++ src/module.ts | 1 + src/types/panel.ts | 5 +++ src/utils/index.ts | 1 + src/utils/locale.ts | 29 ++++++++++++++++ 14 files changed, 163 insertions(+), 21 deletions(-) create mode 100644 src/@types/i18next.d.ts create mode 100644 src/i18n/config.ts create mode 100644 src/i18n/constants.ts create mode 100644 src/i18n/index.ts create mode 100644 src/i18n/translations/en.json create mode 100644 src/i18n/translations/index.ts create mode 100644 src/utils/locale.ts diff --git a/package-lock.json b/package-lock.json index 3a397a9..5ff7ac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,11 @@ "@grafana/ui": "^10.1.4", "@types/react-virtualized-auto-sizer": "^1.0.1", "dayjs": "^1.11.10", + "i18next": "^23.6.0", "react": "18.2.0", "react-big-calendar": "^1.8.4", "react-dom": "18.2.0", + "react-i18next": "^13.3.1", "react-virtualized-auto-sizer": "^1.0.20", "tslib": "^2.6.2" }, @@ -3267,6 +3269,28 @@ "react-dom": "^17.0.0 || ^18.0.0" } }, + "node_modules/@grafana/ui/node_modules/i18next": { + "version": "22.5.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.1.tgz", + "integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, "node_modules/@grafana/ui/node_modules/react-hook-form": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.5.3.tgz", @@ -3279,6 +3303,27 @@ "react": "^16.8.0 || ^17" } }, + "node_modules/@grafana/ui/node_modules/react-i18next": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.3.1.tgz", + "integrity": "sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==", + "dependencies": { + "@babel/runtime": "^7.20.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 19.0.0", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@grafana/ui/node_modules/tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", @@ -11706,9 +11751,9 @@ "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, "node_modules/i18next": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.1.tgz", - "integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.6.0.tgz", + "integrity": "sha512-z0Cxr0MGkt+kli306WS4nNNM++9cgt2b2VCMprY92j+AIab/oclgPxdwtTZVLP1zn5t5uo8M6uLsZmYrcjr3HA==", "funding": [ { "type": "individual", @@ -11724,7 +11769,7 @@ } ], "dependencies": { - "@babel/runtime": "^7.20.6" + "@babel/runtime": "^7.22.5" } }, "node_modules/i18next-browser-languagedetector": { @@ -18162,15 +18207,15 @@ "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" }, "node_modules/react-i18next": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.3.1.tgz", - "integrity": "sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.3.1.tgz", + "integrity": "sha512-JAtYREK879JXaN9GdzfBI4yJeo/XyLeXWUsRABvYXiFUakhZJ40l+kaTo+i+A/3cKIED41kS/HAbZ5BzFtq/Og==", "dependencies": { - "@babel/runtime": "^7.20.6", + "@babel/runtime": "^7.22.5", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { - "i18next": ">= 19.0.0", + "i18next": ">= 23.2.3", "react": ">= 16.8.0" }, "peerDependenciesMeta": { diff --git a/package.json b/package.json index 1700a9a..5c03124 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,11 @@ "@grafana/ui": "^10.1.4", "@types/react-virtualized-auto-sizer": "^1.0.1", "dayjs": "^1.11.10", + "i18next": "^23.6.0", "react": "18.2.0", "react-big-calendar": "^1.8.4", "react-dom": "18.2.0", + "react-i18next": "^13.3.1", "react-virtualized-auto-sizer": "^1.0.20", "tslib": "^2.6.2" }, diff --git a/src/@types/i18next.d.ts b/src/@types/i18next.d.ts new file mode 100644 index 0000000..0cdda58 --- /dev/null +++ b/src/@types/i18next.d.ts @@ -0,0 +1,8 @@ +import { defaultNS, resources } from '../i18n'; + +declare module 'i18next' { + interface CustomTypeOptions { + defaultNS: typeof defaultNS; + resources: typeof resources.en; + } +} diff --git a/src/constants/default.ts b/src/constants/default.ts index 1068cc4..d1955b9 100644 --- a/src/constants/default.ts +++ b/src/constants/default.ts @@ -1,10 +1,10 @@ -import { CalendarOptions, CalendarType, View } from '../types'; +import { CalendarOptions, CalendarType, SupportedLanguage, View } from '../types'; import { AnnotationsType, Colors } from './options'; /** - * Language + * Default Language */ -export const DefaultLanguage = 'en-US'; +export const DefaultLanguage: SupportedLanguage = 'en'; /** * Default Options diff --git a/src/hooks/useLocalizer.ts b/src/hooks/useLocalizer.ts index caf65d1..5a34e88 100644 --- a/src/hooks/useLocalizer.ts +++ b/src/hooks/useLocalizer.ts @@ -7,9 +7,8 @@ import zhLocale from 'dayjs/locale/zh'; import { useEffect, useMemo, useState } from 'react'; import { dayjsLocalizer } from 'react-big-calendar'; import { getLocaleData } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { DefaultLanguage, LanguageMessages } from '../constants'; - +import { LanguageMessages } from '../constants'; +import { getUserLanguage } from '../utils'; /** * Dayjs locales per each grafana language * Dynamic import is not needed until there is too many locales @@ -28,13 +27,12 @@ const dayjsLocales = { */ export const useLocalizer = () => { const localeDate = getLocaleData(); - const language = config.bootData.user.language || DefaultLanguage; - const localeName = language.split('-')[0]; + const language = getUserLanguage(); const [dayjsLocale, setDayjsLocale] = useState(dayjsLocales.en); useEffect(() => { - setDayjsLocale(dayjsLocales[localeName as keyof typeof dayjsLocales] || dayjsLocales.en); - }, [localeName]); + setDayjsLocale(dayjsLocales[language as keyof typeof dayjsLocales] || dayjsLocales.en); + }, [language]); return useMemo(() => { /** @@ -56,6 +54,6 @@ export const useLocalizer = () => { (localizer.formats as any).yearWeekFormat = 'dd'; (localizer.formats as any).yearDateFormat = 'D'; - return { localizer, messages: LanguageMessages[localeName] }; - }, [dayjsLocale, localeDate, localeName]); + return { localizer, messages: LanguageMessages[language] }; + }, [dayjsLocale, localeDate, language]); }; diff --git a/src/i18n/config.ts b/src/i18n/config.ts new file mode 100644 index 0000000..bbdfb36 --- /dev/null +++ b/src/i18n/config.ts @@ -0,0 +1,24 @@ +import i18next from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import { resources } from './translations'; +import { getUserLanguage } from '../utils'; + +/** + * Default Namespace + */ +export const defaultNS = 'translation'; + +/** + * Init i18next + */ +i18next.use(initReactI18next).init({ + lng: getUserLanguage(), + debug: true, + resources, + defaultNS, +}); + +/** + * I18Next instance + */ +export { i18next }; diff --git a/src/i18n/constants.ts b/src/i18n/constants.ts new file mode 100644 index 0000000..9baac20 --- /dev/null +++ b/src/i18n/constants.ts @@ -0,0 +1,9 @@ +/** + * Formats + */ +export const Formats = { + dateWithTime: { + dateStyle: 'short', + timeStyle: 'medium', + }, +}; diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..d3e7e5e --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,3 @@ +export * from './config'; +export * from './constants'; +export * from './translations'; diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/translations/en.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n/translations/index.ts b/src/i18n/translations/index.ts new file mode 100644 index 0000000..7408337 --- /dev/null +++ b/src/i18n/translations/index.ts @@ -0,0 +1,16 @@ +import en from './en.json'; + +/** + * Translation Resources + */ +export const resources = { + /** + * English + */ + en: { + /** + * Translation + */ + translation: en, + }, +} as const; diff --git a/src/module.ts b/src/module.ts index bb84070..1a64f3a 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,4 +1,5 @@ import { Field, FieldConfigProperty, FieldType, PanelPlugin } from '@grafana/data'; +import './i18n'; import { CalendarPanel, MultiFieldEditor } from './components'; import { AnnotationsOptions, diff --git a/src/types/panel.ts b/src/types/panel.ts index c34ee9a..fd9be4a 100644 --- a/src/types/panel.ts +++ b/src/types/panel.ts @@ -1,6 +1,11 @@ import { AnnotationsType, Colors } from '../constants'; import { CalendarType, View } from './calendar'; +/** + * Supported Language + */ +export type SupportedLanguage = 'en' | 'es' | 'fr' | 'de' | 'zh'; + /** * Calendar Options */ diff --git a/src/utils/index.ts b/src/utils/index.ts index ae7dcc6..e8edfd7 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,3 +3,4 @@ export * from './annotations'; export * from './hooks'; export * from './time'; export * from './calendarEvents'; +export * from './locale'; diff --git a/src/utils/locale.ts b/src/utils/locale.ts new file mode 100644 index 0000000..b6de193 --- /dev/null +++ b/src/utils/locale.ts @@ -0,0 +1,29 @@ +import { config } from '@grafana/runtime'; +import { DefaultLanguage } from '../constants'; +import { SupportedLanguage } from '../types'; + +/** + * Get User Language + * @param fallback + */ +export const getUserLanguage = (fallback = DefaultLanguage): SupportedLanguage => { + const locale = config.bootData.user.language; + const lang = locale?.split('-')?.[0]; + + /** + * Validate supported languages + */ + switch (lang) { + case 'en': + case 'es': + case 'fr': + case 'de': + case 'zh': { + return lang; + } + + default: { + return fallback; + } + } +};