diff --git a/package.json b/package.json index 42f0adac0..b14564640 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "yarn build:next && yarn build:server", "lint": "eslint \"./server/**/*.{ts,tsx}\" \"./src/**/*.{ts,tsx}\"", "start": "NODE_ENV=production node dist/index.js", - "i18n:extract": "extract-messages -l=en,ja,fr,nb_NO,de -o src/i18n/locale -d en --flat true --overwriteDefault false './src/**/!(*.test).{ts,tsx}'", + "i18n:extract": "extract-messages -l=en,ja,fr,nb_NO,de,ru -o src/i18n/locale -d en --flat true --overwriteDefault false './src/**/!(*.test).{ts,tsx}'", "migration:generate": "ts-node --project server/tsconfig.json ./node_modules/.bin/typeorm migration:generate", "migration:run": "ts-node --project server/tsconfig.json ./node_modules/.bin/typeorm migration:run", "format": "prettier --write ." diff --git a/src/components/Layout/LanguagePicker/index.tsx b/src/components/Layout/LanguagePicker/index.tsx index 722a5bbc7..7da99d2f0 100644 --- a/src/components/Layout/LanguagePicker/index.tsx +++ b/src/components/Layout/LanguagePicker/index.tsx @@ -37,6 +37,10 @@ const availableLanguages: AvailableLanguageObject = { code: 'de', display: 'German', }, + ru: { + code: 'ru', + display: 'Russian', + }, }; const LanguagePicker: React.FC = () => { @@ -69,10 +73,10 @@ const LanguagePicker: React.FC = () => { diff --git a/src/context/LanguageContext.tsx b/src/context/LanguageContext.tsx index 88762bcfa..3530ebc25 100644 --- a/src/context/LanguageContext.tsx +++ b/src/context/LanguageContext.tsx @@ -1,6 +1,6 @@ import React, { ReactNode } from 'react'; -export type AvailableLocales = 'en' | 'ja' | 'fr' | 'nb-NO' | 'de'; +export type AvailableLocales = 'en' | 'ja' | 'fr' | 'nb-NO' | 'de' | 'ru'; interface LanguageContextProps { locale: AvailableLocales; diff --git a/src/hooks/useClickOutside.ts b/src/hooks/useClickOutside.ts index 211349251..3b8adf35e 100644 --- a/src/hooks/useClickOutside.ts +++ b/src/hooks/useClickOutside.ts @@ -19,7 +19,7 @@ const useClickOutside = ( callback(e); } }; - document.body.addEventListener('click', handleBodyClick); + document.body.addEventListener('click', handleBodyClick, { capture: true }); return () => { document.body.removeEventListener('click', handleBodyClick); diff --git a/src/i18n/locale/ru.json b/src/i18n/locale/ru.json index f3a1d29fb..a76ac9937 100644 --- a/src/i18n/locale/ru.json +++ b/src/i18n/locale/ru.json @@ -1,83 +1,303 @@ { - "components.Settings.SonarrModal.apiKey": "Ключ API", - "components.Settings.SonarrModal.add": "Добавить сервер", - "components.Settings.RadarrModal.validationRootFolderRequired": "Вы должны выбрать корневую папку", - "components.Settings.RadarrModal.validationProfileRequired": "Вы должны выбрать профиль", - "components.Settings.RadarrModal.testing": "Тестирование...", - "components.Settings.RadarrModal.test": "Тест", - "components.Settings.RadarrModal.ssl": "SSL", - "components.Settings.RadarrModal.servername": "Название сервера", - "components.Settings.RadarrModal.selectRootFolder": "Выберите корневую папку", - "components.Settings.RadarrModal.saving": "Сохранение...", - "components.Settings.RadarrModal.save": "Сохранить изменения", - "components.Settings.RadarrModal.rootfolder": "Корневая папка", - "components.Settings.RadarrModal.port": "Порт", - "components.Settings.RadarrModal.hostname": "Имя хоста", - "components.Settings.RadarrModal.defaultserver": "Сервер по умолчанию", - "components.Settings.RadarrModal.apiKey": "Ключ API", - "components.Settings.RadarrModal.add": "Добавить сервер", - "components.Settings.Notifications.saving": "Сохранение...", - "components.Settings.Notifications.save": "Сохранить изменения", - "components.Settings.Notifications.enableSsl": "Включить SSL", - "components.Settings.Notifications.agentenabled": "Агент включен", - "components.Search.searchresults": "Результаты поиска", - "components.RequestModal.selectseason": "Выберите сезон(ы)", - "components.RequestModal.seasonnumber": "Сезон {number}", - "components.RequestModal.season": "Сезон", - "components.RequestModal.requesttitle": "Запрос {title}", - "components.RequestModal.request": "Запрос", - "components.RequestModal.extras": "Дополнительно", - "components.RequestModal.close": "Закрыть", - "components.TvDetails.cancelrequest": "Отменить запрос", + "components.Discover.discovermovies": "Популярные фильмы", + "components.Discover.discovertv": "Популярные серии", + "components.Discover.nopending": "", + "components.Discover.popularmovies": "Популярные фильмы", + "components.Discover.populartv": "Популярные серии", + "components.Discover.recentlyAdded": "Недавно добавленные", + "components.Discover.recentrequests": "Последние запросы", + "components.Discover.trending": "Тренды", + "components.Discover.upcoming": "Предстоящие фильмы", + "components.Discover.upcomingmovies": "Предстоящие фильмы", + "components.Layout.LanguagePicker.changelanguage": "Изменить язык", + "components.Layout.SearchInput.searchPlaceholder": "Поиск фильмов и сериалов", + "components.Layout.Sidebar.dashboard": "", + "components.Layout.Sidebar.requests": "Запросы", + "components.Layout.Sidebar.settings": "Настройки", + "components.Layout.Sidebar.users": "Пользователи", + "components.Layout.UserDropdown.signout": "Выход", + "components.Layout.alphawarning": "", + "components.Login.signinplex": "Войдите, чтобы продолжить", + "components.MovieDetails.approve": "Одобрить", + "components.MovieDetails.available": "Доступно", + "components.MovieDetails.budget": "Бюджет", + "components.MovieDetails.cancelrequest": "Отменить запрос", + "components.MovieDetails.cast": "В ролях", + "components.MovieDetails.decline": "", + "components.MovieDetails.manageModalClearMedia": "Очистить все медиаданные", + "components.MovieDetails.manageModalClearMediaWarning": "", + "components.MovieDetails.manageModalNoRequests": "Нет запросов", + "components.MovieDetails.manageModalRequests": "Запросы", + "components.MovieDetails.manageModalTitle": "", + "components.MovieDetails.originallanguage": "Оригинальный язык", + "components.MovieDetails.overview": "Обзор", + "components.MovieDetails.overviewunavailable": "", + "components.MovieDetails.pending": "В ожидании", + "components.MovieDetails.recommendations": "Рекомендации", + "components.MovieDetails.recommendationssubtext": "", + "components.MovieDetails.releasedate": "Дата выпуска", + "components.MovieDetails.request": "Запрос", + "components.MovieDetails.revenue": "Доход", + "components.MovieDetails.runtime": "{minutes} минут", + "components.MovieDetails.similar": "", + "components.MovieDetails.similarsubtext": "", + "components.MovieDetails.status": "Статус", + "components.MovieDetails.unavailable": "Недоступен", + "components.MovieDetails.userrating": "Пользовательский рейтинг", + "components.MovieDetails.viewrequest": "Просмотр запроса", + "components.PlexLoginButton.loading": "Загрузка...", + "components.PlexLoginButton.loggingin": "", + "components.PlexLoginButton.loginwithplex": "Войти через Plex", + "components.RequestBlock.seasons": "Сезонов", + "components.RequestCard.all": "", + "components.RequestCard.requestedby": "По запросу {username}", + "components.RequestCard.seasons": "Сезонов", + "components.RequestList.RequestItem.notavailable": "", + "components.RequestList.RequestItem.requestedby": "По запросу {username}", + "components.RequestList.RequestItem.seasons": "Сезонов", + "components.RequestList.mediaInfo": "", + "components.RequestList.modifiedBy": "Автор последнего изменения", + "components.RequestList.next": "Следующий", + "components.RequestList.previous": "Предыдущий", + "components.RequestList.requestedAt": "", + "components.RequestList.requests": "Запросы", + "components.RequestList.showingresults": "", + "components.RequestList.status": "Статус", "components.RequestModal.cancel": "Отменить запрос", + "components.RequestModal.cancelling": "", + "components.RequestModal.cancelrequest": "", + "components.RequestModal.close": "Закрыть", + "components.RequestModal.extras": "Дополнительно", + "components.RequestModal.notrequested": "", + "components.RequestModal.numberofepisodes": "", + "components.RequestModal.pendingrequest": "", + "components.RequestModal.request": "Запрос", + "components.RequestModal.requestCancel": "", + "components.RequestModal.requestSuccess": "", + "components.RequestModal.requestadmin": "", + "components.RequestModal.requestfrom": "", + "components.RequestModal.requesting": "", + "components.RequestModal.requestseasons": "", + "components.RequestModal.requesttitle": "Запрос {title}", + "components.RequestModal.season": "Сезон", + "components.RequestModal.seasonnumber": "Сезон {number}", + "components.RequestModal.selectseason": "Выберите сезон(ы)", "components.RequestModal.status": "Статус", + "components.Search.searchresults": "Результаты поиска", + "components.Settings.Notifications.agentenabled": "Агент включен", + "components.Settings.Notifications.authPass": "", + "components.Settings.Notifications.authUser": "", + "components.Settings.Notifications.emailsender": "", + "components.Settings.Notifications.enableSsl": "Включить SSL", + "components.Settings.Notifications.save": "Сохранить изменения", + "components.Settings.Notifications.saving": "Сохранение...", + "components.Settings.Notifications.smtpHost": "", + "components.Settings.Notifications.smtpPort": "", + "components.Settings.Notifications.validationFromRequired": "", + "components.Settings.Notifications.validationSmtpHostRequired": "", + "components.Settings.Notifications.validationSmtpPortRequired": "", + "components.Settings.Notifications.validationWebhookUrlRequired": "", + "components.Settings.Notifications.webhookUrl": "", + "components.Settings.Notifications.webhookUrlPlaceholder": "", + "components.Settings.RadarrModal.add": "Добавить сервер", + "components.Settings.RadarrModal.apiKey": "Ключ API", + "components.Settings.RadarrModal.apiKeyPlaceholder": "", + "components.Settings.RadarrModal.baseUrl": "", + "components.Settings.RadarrModal.baseUrlPlaceholder": "", + "components.Settings.RadarrModal.createradarr": "", + "components.Settings.RadarrModal.defaultserver": "Сервер по умолчанию", + "components.Settings.RadarrModal.editradarr": "", + "components.Settings.RadarrModal.hostname": "Имя хоста", + "components.Settings.RadarrModal.minimumAvailability": "", + "components.Settings.RadarrModal.port": "Порт", + "components.Settings.RadarrModal.qualityprofile": "", + "components.Settings.RadarrModal.rootfolder": "Корневая папка", + "components.Settings.RadarrModal.save": "Сохранить изменения", + "components.Settings.RadarrModal.saving": "Сохранение...", + "components.Settings.RadarrModal.selectMinimumAvailability": "", + "components.Settings.RadarrModal.selectQualityProfile": "", + "components.Settings.RadarrModal.selectRootFolder": "Выберите корневую папку", + "components.Settings.RadarrModal.server4k": "", + "components.Settings.RadarrModal.servername": "Название сервера", + "components.Settings.RadarrModal.servernamePlaceholder": "", + "components.Settings.RadarrModal.ssl": "SSL", + "components.Settings.RadarrModal.test": "Тест", + "components.Settings.RadarrModal.testing": "Тестирование...", + "components.Settings.RadarrModal.toastRadarrTestFailure": "", + "components.Settings.RadarrModal.toastRadarrTestSuccess": "", + "components.Settings.RadarrModal.validationApiKeyRequired": "", + "components.Settings.RadarrModal.validationHostnameRequired": "", + "components.Settings.RadarrModal.validationPortRequired": "", + "components.Settings.RadarrModal.validationProfileRequired": "Вы должны выбрать профиль", + "components.Settings.RadarrModal.validationRootFolderRequired": "Вы должны выбрать корневую папку", + "components.Settings.SonarrModal.add": "Добавить сервер", + "components.Settings.SonarrModal.apiKey": "Ключ API", + "components.Settings.SonarrModal.apiKeyPlaceholder": "", + "components.Settings.SonarrModal.baseUrl": "", + "components.Settings.SonarrModal.baseUrlPlaceholder": "", + "components.Settings.SonarrModal.createsonarr": "", + "components.Settings.SonarrModal.defaultserver": "", + "components.Settings.SonarrModal.editsonarr": "", + "components.Settings.SonarrModal.hostname": "", + "components.Settings.SonarrModal.port": "", + "components.Settings.SonarrModal.qualityprofile": "", + "components.Settings.SonarrModal.rootfolder": "", + "components.Settings.SonarrModal.save": "", + "components.Settings.SonarrModal.saving": "", + "components.Settings.SonarrModal.seasonfolders": "", + "components.Settings.SonarrModal.selectQualityProfile": "", + "components.Settings.SonarrModal.selectRootFolder": "", + "components.Settings.SonarrModal.server4k": "", + "components.Settings.SonarrModal.servername": "", + "components.Settings.SonarrModal.servernamePlaceholder": "", + "components.Settings.SonarrModal.ssl": "", + "components.Settings.SonarrModal.test": "", + "components.Settings.SonarrModal.testing": "", + "components.Settings.SonarrModal.toastRadarrTestFailure": "", + "components.Settings.SonarrModal.toastRadarrTestSuccess": "", + "components.Settings.SonarrModal.validationApiKeyRequired": "", + "components.Settings.SonarrModal.validationHostnameRequired": "", + "components.Settings.SonarrModal.validationPortRequired": "", + "components.Settings.SonarrModal.validationProfileRequired": "", + "components.Settings.SonarrModal.validationRootFolderRequired": "", + "components.Settings.activeProfile": "", + "components.Settings.addradarr": "", + "components.Settings.address": "", + "components.Settings.addsonarr": "", + "components.Settings.apikey": "", + "components.Settings.applicationurl": "", + "components.Settings.cancelscan": "", + "components.Settings.copied": "", + "components.Settings.currentlibrary": "", + "components.Settings.default": "", + "components.Settings.default4k": "", + "components.Settings.delete": "", + "components.Settings.deleteserverconfirm": "", + "components.Settings.edit": "", + "components.Settings.generalsettings": "", + "components.Settings.generalsettingsDescription": "", + "components.Settings.hostname": "", + "components.Settings.jobname": "", + "components.Settings.librariesRemaining": "", + "components.Settings.manualscan": "", + "components.Settings.manualscanDescription": "", + "components.Settings.menuAbout": "", + "components.Settings.menuGeneralSettings": "", + "components.Settings.menuJobs": "", + "components.Settings.menuLogs": "", + "components.Settings.menuNotifications": "", + "components.Settings.menuPlexSettings": "", + "components.Settings.menuServices": "", + "components.Settings.nextexecution": "", + "components.Settings.notificationsettings": "", + "components.Settings.notificationsettingsDescription": "", + "components.Settings.notrunning": "", + "components.Settings.plexlibraries": "", + "components.Settings.plexlibrariesDescription": "", + "components.Settings.plexsettings": "", + "components.Settings.plexsettingsDescription": "", + "components.Settings.port": "", + "components.Settings.radarrSettingsDescription": "", + "components.Settings.radarrsettings": "", + "components.Settings.runnow": "", + "components.Settings.save": "", + "components.Settings.saving": "", + "components.Settings.servername": "", + "components.Settings.servernamePlaceholder": "", + "components.Settings.sonarrSettingsDescription": "", + "components.Settings.sonarrsettings": "", + "components.Settings.ssl": "", + "components.Settings.startscan": "", + "components.Settings.sync": "", + "components.Settings.syncing": "", + "components.Setup.configureplex": "", + "components.Setup.configureservices": "", + "components.Setup.continue": "", + "components.Setup.finish": "", + "components.Setup.finishing": "", + "components.Setup.loginwithplex": "", + "components.Setup.signinMessage": "", + "components.Setup.welcome": "", + "components.Slider.noresults": "", + "components.TitleCard.movie": "", + "components.TitleCard.tvshow": "", + "components.TvDetails.approve": "", + "components.TvDetails.approverequests": "", + "components.TvDetails.available": "", + "components.TvDetails.cancelrequest": "Отменить запрос", + "components.TvDetails.cast": "", + "components.TvDetails.decline": "", + "components.TvDetails.declinerequests": "", + "components.TvDetails.manageModalClearMedia": "", + "components.TvDetails.manageModalClearMediaWarning": "", + "components.TvDetails.manageModalNoRequests": "", + "components.TvDetails.manageModalRequests": "", + "components.TvDetails.manageModalTitle": "", + "components.TvDetails.originallanguage": "", + "components.TvDetails.overview": "", + "components.TvDetails.overviewunavailable": "", + "components.TvDetails.pending": "", + "components.TvDetails.recommendations": "", + "components.TvDetails.recommendationssubtext": "", + "components.TvDetails.request": "", + "components.TvDetails.requestmore": "", + "components.TvDetails.similar": "", + "components.TvDetails.similarsubtext": "", "components.TvDetails.status": "Статус", - "components.RequestList.status": "Статус", - "components.RequestList.requests": "Запросы", - "components.RequestList.previous": "Предыдущий", - "components.RequestList.next": "Следующий", - "components.RequestList.modifiedBy": "Автор последнего изменения", - "components.RequestList.RequestItem.seasons": "Сезонов", - "components.RequestList.RequestItem.requestedby": "По запросу {username}", - "components.RequestCard.seasons": "Сезонов", - "components.RequestCard.requestedby": "По запросу {username}", - "components.RequestBlock.seasons": "Сезонов", - "components.PlexLoginButton.loginwithplex": "Войти через Plex", - "components.PlexLoginButton.loading": "Загрузка...", - "components.MovieDetails.viewrequest": "Просмотр запроса", - "components.MovieDetails.userrating": "Пользовательский рейтинг", - "components.MovieDetails.unavailable": "Недоступен", - "components.MovieDetails.status": "Статус", - "components.MovieDetails.runtime": "{minutes} минут", - "components.MovieDetails.revenue": "Доход", - "components.MovieDetails.request": "Запрос", - "components.MovieDetails.releasedate": "Дата выпуска", - "components.MovieDetails.recommendations": "Рекомендации", - "components.MovieDetails.pending": "В ожидании", - "components.MovieDetails.overview": "Обзор", - "components.MovieDetails.originallanguage": "Оригинальный язык", - "components.MovieDetails.manageModalRequests": "Запросы", - "components.MovieDetails.manageModalNoRequests": "Нет запросов", - "components.MovieDetails.manageModalClearMedia": "Очистить все медиаданные", - "components.MovieDetails.cast": "В ролях", - "components.MovieDetails.cancelrequest": "Отменить запрос", - "components.MovieDetails.budget": "Бюджет", - "components.MovieDetails.available": "Доступно", - "components.MovieDetails.approve": "Одобрить", - "components.Login.signinplex": "Войдите, чтобы продолжить", - "components.Layout.UserDropdown.signout": "Выход", - "components.Layout.Sidebar.users": "Пользователи", - "components.Layout.Sidebar.settings": "Настройки", - "components.Layout.Sidebar.requests": "Запросы", - "components.Layout.SearchInput.searchPlaceholder": "Поиск фильмов и сериалов", - "components.Layout.LanguagePicker.changelanguage": "Изменить язык", - "components.Discover.upcomingmovies": "Предстоящие фильмы", - "components.Discover.upcoming": "Предстоящие фильмы", - "components.Discover.trending": "Тренды", - "components.Discover.recentrequests": "Последние запросы", - "components.Discover.recentlyAdded": "Недавно добавленные", - "components.Discover.populartv": "Популярные серии", - "components.Discover.popularmovies": "Популярные фильмы", - "components.Discover.discovertv": "Популярные серии", - "components.Discover.discovermovies": "Популярные фильмы" + "components.TvDetails.unavailable": "", + "components.TvDetails.userrating": "", + "components.UserEdit.admin": "", + "components.UserEdit.adminDescription": "", + "components.UserEdit.autoapprove": "", + "components.UserEdit.autoapproveDescription": "", + "components.UserEdit.avatar": "", + "components.UserEdit.edituser": "", + "components.UserEdit.email": "", + "components.UserEdit.managerequests": "", + "components.UserEdit.managerequestsDescription": "", + "components.UserEdit.permissions": "", + "components.UserEdit.request": "", + "components.UserEdit.requestDescription": "", + "components.UserEdit.save": "", + "components.UserEdit.saving": "", + "components.UserEdit.settings": "", + "components.UserEdit.settingsDescription": "", + "components.UserEdit.userfail": "", + "components.UserEdit.username": "", + "components.UserEdit.users": "", + "components.UserEdit.usersDescription": "", + "components.UserEdit.usersaved": "", + "components.UserEdit.vote": "", + "components.UserEdit.voteDescription": "", + "components.UserList.admin": "", + "components.UserList.created": "", + "components.UserList.delete": "", + "components.UserList.edit": "", + "components.UserList.lastupdated": "", + "components.UserList.plexuser": "", + "components.UserList.role": "", + "components.UserList.totalrequests": "", + "components.UserList.user": "", + "components.UserList.userlist": "", + "components.UserList.username": "", + "components.UserList.usertype": "", + "i18n.approve": "", + "i18n.approved": "", + "i18n.available": "", + "i18n.cancel": "", + "i18n.decline": "", + "i18n.declined": "", + "i18n.delete": "", + "i18n.movies": "", + "i18n.partiallyavailable": "", + "i18n.pending": "", + "i18n.processing": "", + "i18n.tvshows": "", + "i18n.unavailable": "", + "pages.internalServerError": "", + "pages.oops": "", + "pages.pageNotFound": "", + "pages.returnHome": "", + "pages.serviceUnavailable": "", + "pages.somethingWentWrong": "" } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 0d1cb4e7d..7d32d6dbb 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -24,6 +24,8 @@ const loadLocaleData = (locale: string): Promise => { return import('../i18n/locale/nb_NO.json'); case 'de': return import('../i18n/locale/de.json'); + case 'ru': + return import('../i18n/locale/ru.json'); default: return import('../i18n/locale/en.json'); }