diff --git a/package-lock.json b/package-lock.json index dfc1b747..291d2b2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "classnames": "^2.3.2", "firebase": "^10.1.0", "gsap": "^3.12.2", - "i18n-js": "^4.3.0", + "i18next": "^23.7.11", "locomotive-scroll": "^4.1.4", "lodash": "^4.17.21", "react": "^18.2.0", @@ -2052,16 +2052,21 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", - "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/@babel/template": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", @@ -7826,14 +7831,6 @@ "node": "*" } }, - "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -11748,13 +11745,26 @@ "node": ">=10.17.0" } }, - "node_modules/i18n-js": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-4.3.0.tgz", - "integrity": "sha512-PX93eT6WPV6Ym6mHtFKGDRZB0zwDX7HUPkgprjsZ28J6/Ohw1nvRYuM93or3pWv2VLxs6XfBf7X9Fc/YAZNEtQ==", + "node_modules/i18next": { + "version": "23.7.11", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.7.11.tgz", + "integrity": "sha512-A/vOkw8vY99YHU9A1Td3I1dcTiYaPnwBWzrpVzfXUXSYgogK3cmBcmop/0cnXPc6QpUWIyqaugKNxRUEZVk9Nw==", + "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": { - "bignumber.js": "*", - "make-plural": "*" + "@babel/runtime": "^7.23.2" } }, "node_modules/iconv-lite": { @@ -15263,11 +15273,6 @@ "semver": "bin/semver.js" } }, - "node_modules/make-plural": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.3.0.tgz", - "integrity": "sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw==" - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -24126,11 +24131,18 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "@babel/runtime": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", - "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } } }, "@babel/template": { @@ -28502,11 +28514,6 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, - "bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -31378,13 +31385,12 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" }, - "i18n-js": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-4.3.0.tgz", - "integrity": "sha512-PX93eT6WPV6Ym6mHtFKGDRZB0zwDX7HUPkgprjsZ28J6/Ohw1nvRYuM93or3pWv2VLxs6XfBf7X9Fc/YAZNEtQ==", + "i18next": { + "version": "23.7.11", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.7.11.tgz", + "integrity": "sha512-A/vOkw8vY99YHU9A1Td3I1dcTiYaPnwBWzrpVzfXUXSYgogK3cmBcmop/0cnXPc6QpUWIyqaugKNxRUEZVk9Nw==", "requires": { - "bignumber.js": "*", - "make-plural": "*" + "@babel/runtime": "^7.23.2" } }, "iconv-lite": { @@ -33945,11 +33951,6 @@ } } }, - "make-plural": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.3.0.tgz", - "integrity": "sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw==" - }, "makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", diff --git a/package.json b/package.json index 0aa0179f..fe605957 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "classnames": "^2.3.2", "firebase": "^10.1.0", "gsap": "^3.12.2", - "i18n-js": "^4.3.0", + "i18next": "^23.7.11", "locomotive-scroll": "^4.1.4", "lodash": "^4.17.21", "react": "^18.2.0", diff --git a/src/assets/translations/ru.json b/src/assets/translations/ru.json index f5ca2717..21c0540d 100644 --- a/src/assets/translations/ru.json +++ b/src/assets/translations/ru.json @@ -74,7 +74,7 @@ "profile": { "title": "Профиль", "courseGroupTitle":"Ваши онлайн-курсы", - "endDate": "Доступ до %{date}", + "endDate": "Доступ до {{date}}", "noCoursesFallback": { "title": "У вас пока нет онлайн-курсов", "suggestionLink": "Посетите главную страницу", @@ -88,21 +88,27 @@ "courseLanding": { "programIntro" : { "free": "Бесплатно", - "datesInfoLabel": "%{startDate} | %{durationInUnits} %{unitPlural}" + "datesInfoLabel": "{{startDate}} | {{durationInUnits}} $t({{unit}}.p, {\"count\": {{durationInUnits}} })" }, "form": { - "title:course": "Записаться на онлайн курс", - "title:webinar": "Записаться на онлайн интенсив", + "title": { + "course": "Записаться на онлайн курс", + "webinar": "Записаться на онлайн интенсив" + }, "subtitle": "Заполните форму и я свяжусь с вами, чтобы оформить оплату", - "courseName": " “%{courseName}”", + "courseName": " “{{courseName}}”", "emailCaption": "Адрес, к которому хотите привязать покупку. Если у вас есть профиль с другими покупками, используйте адрес оттуда", "agreement": "Оферта и Договор", - "orderIsCreated:course": "Мы получили вашу заявку! Сегодня на почту %{email} мы отправим письмо с информацией об оплате.", - "orderIsCreated:webinar": "Мы получили вашу заявку! Сегодня на почту %{email} мы отправим письмо с информацией о том, как получить доступ к материалам интенсива." + "orderIsCreated": { + "course": "Мы получили вашу заявку! Сегодня на почту {{email}} мы отправим письмо с информацией об оплате.", + "webinar": "Мы получили вашу заявку! Сегодня на почту {{email}} мы отправим письмо с информацией о том, как получить доступ к материалам интенсива." + } }, "description": { - "title:course": "Курс подойдeт тем, кто", - "title:webinar": "Интенсив подойдeт тем, кто" + "title": { + "course": "Курс подойдeт тем, кто", + "webinar": "Интенсив подойдeт тем, кто" + } }, "prizes": { "title:course": "Подарки для участников курса", @@ -110,7 +116,7 @@ }, "discountBanner": { "discount": "скидка", - "description": "Успейте записаться\nсо скидкой\nдо %{deadline} по Мск", + "description": "Успейте записаться\nсо скидкой\nдо {{deadline}} по Мск", "day": "д", "hour": "ч", "minute": "м", @@ -136,28 +142,30 @@ "modules": { "title": "Программа", "videosNumber": { - "one": "%{count} видео-урок", - "few": "%{count} видео-урока", - "many": "%{count} видео-уроков", - "other": "%{count} видео-уроков" + "p_one": "{{count}} видео-урок", + "p_few": "{{count}} видео-урока", + "p_many": "{{count}} видео-уроков", + "p_other": "{{count}} видео-уроков" }, "homeworksNumber": { - "one": "%{count} практическое задание", - "few": "%{count} практических заданий", - "many": "%{count} практических заданий", - "other": "%{count} практических заданий" + "p_one": "{{count}} практическое задание", + "p_few": "{{count}} практических заданий", + "p_many": "{{count}} практических заданий", + "p_other": "{{count}} практических заданий" }, - "duration:week": { - "one": "%{count} учебная неделя", - "few": "%{count} учебные недели", - "many": "%{count} учебных недель", - "other": "%{count} учебных недель" - }, - "duration:day": { - "one": "%{count} учебный день", - "few": "%{count} учебных дня", - "many": "%{count} учебных дней", - "other": "%{count} учебных дней" + "duration": { + "day": { + "p_one": "{{count}} учебный день", + "p_few": "{{count}} учебных дня", + "p_many": "{{count}} учебных дней", + "p_other": "{{count}} учебных дней" + }, + "week": { + "p_one": "{{count}} учебная неделя", + "p_few": "{{count}} учебные недели", + "p_many": "{{count}} учебных недель", + "p_other": "{{count}} учебных недель" + } }, "feedback": "индивидуальная обратная связь по дз", "chat": "телеграм-чат для общения в группе" @@ -186,8 +194,8 @@ "navTabsPractice": "Задание", "navTabsResults": "Итоги", "navToLessons": "Все уроки", - "uploadDeadline": "Дедлайн: %{date}\u00A0по\u00A0Мск", - "resultsDeadline": "Публикация итогов: %{date}", + "uploadDeadline": "Дедлайн: {{date}}\u00A0по\u00A0Мск", + "resultsDeadline": "Публикация итогов: {{date}}", "uploadBtn": "Загрузить работу", "myWorkLinkTitle": "Моя работа", "editTitle": "Редактировать...", @@ -218,33 +226,25 @@ "showMoreBtn": "показать больше", "showLessBtn": "свернуть" }, - "fallback:lessonNotStartedYet": "Этот урок пока не начался. Он начнётся %{startDate}, будем вас ждать", - "fallback:noResults:beforeDeadline": "Дата публикации результатов %{resultsEndDate}. Приходите позже.", + "fallback:lessonNotStartedYet": "Этот урок пока не начался. Он начнётся {{startDate}}, будем вас ждать", + "fallback:noResults:beforeDeadline": "Дата публикации результатов {{resultsEndDate}}. Приходите позже.", "fallback:noResults:deadlineDay": "Результатов пока нет, но они должны появится сегодня.", "fallback:noResults:pastDeadline": "Извиняемся за задержку результатов, мы их активно проверяем. Если задержка более дня, напишите, пожалуйста, в телеграм-чат курса." }, "day": { - "one": "день", - "few": "дня", - "many": "дней", - "other": "дней" + "p_one": "день", + "p_few": "дня", + "p_many": "дней", + "p_other": "дней" }, "week": { - "one": "неделя", - "few": "недели", - "many": "недель", - "other": "недель" - }, - "duration:week": { - "one": "%{count} неделя", - "few": "%{count} недели", - "many": "%{count} недель", - "other": "%{count} недель" + "p_one": "неделя", + "p_few": "недели", + "p_many": "недель", + "p_other": "недель" }, - "duration:day": { - "one": "%{count} день", - "few": "%{count} дня", - "many": "%{count} дней", - "other": "%{count} дней" + "duration": { + "week": "{{count}} $t(week.p, {\"count\": {{count}} })", + "day": "{{count}} $t(day.p, {\"count\": {{count}} })" } } \ No newline at end of file diff --git a/src/pages/Course/Landing/ProgramBlocs/DecisionForm/DecisionForm.tsx b/src/pages/Course/Landing/ProgramBlocs/DecisionForm/DecisionForm.tsx index 562d4bf4..b6c8adbe 100644 --- a/src/pages/Course/Landing/ProgramBlocs/DecisionForm/DecisionForm.tsx +++ b/src/pages/Course/Landing/ProgramBlocs/DecisionForm/DecisionForm.tsx @@ -29,12 +29,12 @@ function DecisionForm(props: IProps) {
-
{t(`title:${type}`)}
+
{t(`title.${type}`)}

{t('courseName', { courseName: props.data.title })}

{formatCourseDate(props.data.startDate, props.data.endDate)}
-
{i18n.t(`duration:${duration.unit}`, { count: duration.value })}
+
{i18n.t(`duration.${duration.unit}`, { count: duration.value })}
@@ -46,7 +46,7 @@ function DecisionForm(props: IProps) {
- {orderEmail ? {t(`orderIsCreated:${type}`, { email: orderEmail })} + {orderEmail ? {t(`orderIsCreated.${type}`, { email: orderEmail })} : (<>
setOrderEmail(email)}/>
diff --git a/src/pages/Course/Landing/ProgramBlocs/Description/Description.tsx b/src/pages/Course/Landing/ProgramBlocs/Description/Description.tsx index 1b4919f5..d6b29953 100644 --- a/src/pages/Course/Landing/ProgramBlocs/Description/Description.tsx +++ b/src/pages/Course/Landing/ProgramBlocs/Description/Description.tsx @@ -22,7 +22,7 @@ function Description(props: IProps) { {(id, className) => (

- {t(`title:${props.data.type}`)} + {t(`title.${props.data.type}`)}

)}
diff --git a/src/pages/Course/Landing/ProgramBlocs/Modules/Modules.tsx b/src/pages/Course/Landing/ProgramBlocs/Modules/Modules.tsx index 90184f8e..376614b6 100644 --- a/src/pages/Course/Landing/ProgramBlocs/Modules/Modules.tsx +++ b/src/pages/Course/Landing/ProgramBlocs/Modules/Modules.tsx @@ -27,9 +27,9 @@ function Modules(props: IProps) {

{t('title')}

{}
-
{t('videosNumber', { count: videosNumber })}
-
{t('homeworksNumber', { count: homeworksNumber })}
-
{t(`duration:${duration.unit}`, { count: duration.value })}
+
{t('videosNumber.p', { count: videosNumber })}
+
{t('homeworksNumber.p', { count: homeworksNumber })}
+
{t(`duration.${duration.unit}.p`, { count: duration.value })}
{t('feedback')}
{t('chat')}
diff --git a/src/pages/Course/Landing/ProgramIntro/ProgramIntro.tsx b/src/pages/Course/Landing/ProgramIntro/ProgramIntro.tsx index 0f356778..ddd8d7b6 100644 --- a/src/pages/Course/Landing/ProgramIntro/ProgramIntro.tsx +++ b/src/pages/Course/Landing/ProgramIntro/ProgramIntro.tsx @@ -22,7 +22,7 @@ function ProgramIntro(props: IProps) { t('datesInfoLabel', { startDate: formatDate(startDate, { timeZone: 'Europe/Moscow' }), durationInUnits: duration.value, - unitPlural: i18n.t(duration.unit, { count: duration.value }), + unit: duration.unit, }), !creditPrice && !discontDeadline && t('free'), ].filter(Boolean); diff --git a/src/shared/translations.ts b/src/shared/translations.ts index 8ce03515..ee480152 100644 --- a/src/shared/translations.ts +++ b/src/shared/translations.ts @@ -1,36 +1,56 @@ -import { I18n, TranslateOptions } from 'i18n-js'; +import i18next, { TFunction } from 'i18next'; import ru from 'assets/translations/ru.json'; -const i18n = new I18n({ ru }); -i18n.defaultLocale = 'ru'; -i18n.locale = 'ru'; - -i18n.pluralization.register('ru', (_i18n, count) => { - const mod10 = count % 10; - const mod100 = count % 100; - let key; - - const one = mod10 === 1 && mod100 !== 11; - const few = [2, 3, 4].includes(mod10) && ![12, 13, 14].includes(mod100); - const many = - mod10 === 0 || - [5, 6, 7, 8, 9].includes(mod10) || - [11, 12, 13, 14].includes(mod100); - - if (one) { - key = 'one'; - } else if (few) { - key = 'few'; - } else if (many) { - key = 'many'; - } else { - key = 'other'; +i18next.init({ + lng: 'ru', + fallbackLng: 'ru', + debug: true, + resources: { + ru: { + translation: ru, + } } - - return [key]; }); -const formatI18nT = (keyStart: string) => (keyEnd: string, options?: TranslateOptions ) => i18n.t(`${keyStart}.${keyEnd}`, options); +// const i18n = new I18n({ ru }); +// i18n.defaultLocale = 'ru'; +// i18n.locale = 'ru'; + +// i18n.pluralization.register('ru', (_i18n, count) => { +// const mod10 = count % 10; +// const mod100 = count % 100; +// let key; + +// const one = mod10 === 1 && mod100 !== 11; +// const few = [2, 3, 4].includes(mod10) && ![12, 13, 14].includes(mod100); +// const many = +// mod10 === 0 || +// [5, 6, 7, 8, 9].includes(mod10) || +// [11, 12, 13, 14].includes(mod100); + +// if (one) { +// key = 'one'; +// } else if (few) { +// key = 'few'; +// } else if (many) { +// key = 'many'; +// } else { +// key = 'other'; +// } + +// return [key]; +// }); + +const s = i18next.t; +type Ttargs = Parameters; +const formatI18nT = (keyStart: string) => (...args: Ttargs) => { + const keyEnd = args[0] as string | string[]; + const [, ...restArgs] = args; + const key = [keyStart, ...(typeof keyEnd === 'string' ? [keyEnd] : keyEnd)].join('.'); + return i18next.t(key, ...restArgs); +} + +const i18n = i18next; export { i18n, formatI18nT };