From dc1c2130f856c6175846f67587cadcb549df50a6 Mon Sep 17 00:00:00 2001 From: adi-herwana-nus Date: Mon, 8 Dec 2025 11:25:44 +0800 Subject: [PATCH 1/9] feat(translations): add component translation keys to FE - reorganized client/app/bundles/course/translations.intl.js, added missing keys and converted to TS - modified BE component / sidebar item routes to no longer return default titles - modified FE to fetch default title if none or blank title received from BE --- .../concerns/component_settings_concern.rb | 2 +- .../component_settings/edit.json.jbuilder | 4 +- .../admin/sidebar_settings/show.json.jbuilder | 1 + .../instance/components/index.json.jbuilder | 5 +- .../admin/components/SettingsNavigation.tsx | 24 +- .../ComponentSettingsForm.tsx | 4 +- .../pages/NotificationSettings/index.jsx | 3 +- .../NotificationSettings/translations.intl.js | 4 + .../SidebarSettings/SidebarSettingsForm.tsx | 3 +- .../course/container/Sidebar/SidebarItem.tsx | 20 +- .../AchievementsListing.jsx | 4 +- .../AssessmentsListing.jsx | 4 +- .../MaterialsListing.jsx | 4 +- .../SurveyListing.jsx | 4 +- .../VideosListing.jsx | 4 +- .../ItemsSelector/AchievementsSelector.jsx | 4 +- .../ItemsSelector/AssessmentsSelector.jsx | 4 +- .../ItemsSelector/MaterialsSelector.jsx | 4 +- .../ItemsSelector/SurveysSelector.jsx | 4 +- .../ItemsSelector/VideosSelector.jsx | 4 +- .../Duplication/ItemsSelectorMenu/index.jsx | 4 +- .../course/level/pages/LevelsIndex/index.tsx | 4 +- .../app/bundles/course/translations.intl.js | 49 ---- client/app/bundles/course/translations.ts | 219 ++++++++++++++++++ .../pages/InstanceComponentsIndex.tsx | 32 +-- client/app/types/course/admin/components.ts | 1 - client/app/types/course/admin/course.ts | 3 +- client/app/types/course/admin/sidebar.ts | 2 +- client/app/types/course/courses.ts | 2 +- .../app/types/system/instance/components.ts | 1 - client/locales/en.json | 101 +++++++- client/locales/ko.json | 101 +++++++- client/locales/zh.json | 101 +++++++- 33 files changed, 597 insertions(+), 133 deletions(-) delete mode 100644 client/app/bundles/course/translations.intl.js create mode 100644 client/app/bundles/course/translations.ts diff --git a/app/models/concerns/component_settings_concern.rb b/app/models/concerns/component_settings_concern.rb index a37d22211ba..04c00759687 100644 --- a/app/models/concerns/component_settings_concern.rb +++ b/app/models/concerns/component_settings_concern.rb @@ -4,7 +4,7 @@ module ComponentSettingsConcern # This is used when generating checkboxes for each of the components def disableable_component_collection - @settable.disableable_components.map { |c| [c.display_name, c.key.to_s] }.sort + @settable.disableable_components.map { |c| c.key.to_s } end # Returns the ids of enabled components that can be disabled diff --git a/app/views/course/admin/component_settings/edit.json.jbuilder b/app/views/course/admin/component_settings/edit.json.jbuilder index 1bd583cad0e..0fc3b15905c 100644 --- a/app/views/course/admin/component_settings/edit.json.jbuilder +++ b/app/views/course/admin/component_settings/edit.json.jbuilder @@ -2,9 +2,7 @@ components = @settings.disableable_component_collection enabled_components = @settings.enabled_component_ids.to_set -json.array! components do |component| - id = component[1] +json.array! components do |id| json.id id - json.displayName component[0] json.enabled enabled_components.include?(id) end diff --git a/app/views/course/admin/sidebar_settings/show.json.jbuilder b/app/views/course/admin/sidebar_settings/show.json.jbuilder index b135268d61c..e676a72fa47 100644 --- a/app/views/course/admin/sidebar_settings/show.json.jbuilder +++ b/app/views/course/admin/sidebar_settings/show.json.jbuilder @@ -1,6 +1,7 @@ # frozen_string_literal: true json.array! controller.sidebar_items(type: :settings) do |option| json.title option[:title] + json.id option[:key] json.weight option[:weight] json.path option[:path] end diff --git a/app/views/system/admin/instance/components/index.json.jbuilder b/app/views/system/admin/instance/components/index.json.jbuilder index 1facefe6169..cbf50cfa22f 100644 --- a/app/views/system/admin/instance/components/index.json.jbuilder +++ b/app/views/system/admin/instance/components/index.json.jbuilder @@ -4,8 +4,7 @@ enabled_components = @settings.enabled_component_ids.to_set json.components do json.array! components do |component| - json.key component[1] - json.displayName component[0] - json.enabled enabled_components.include?(component[1]) + json.key component + json.enabled enabled_components.include?(component) end end diff --git a/client/app/bundles/course/admin/components/SettingsNavigation.tsx b/client/app/bundles/course/admin/components/SettingsNavigation.tsx index d9af55245f6..0db2582ed90 100644 --- a/client/app/bundles/course/admin/components/SettingsNavigation.tsx +++ b/client/app/bundles/course/admin/components/SettingsNavigation.tsx @@ -14,6 +14,12 @@ import CourseAPI from 'api/course'; import Page from 'lib/components/core/layouts/Page'; import LoadingIndicator from 'lib/components/core/LoadingIndicator'; import { DataHandle } from 'lib/hooks/router/dynamicNest'; +import useTranslation from 'lib/hooks/useTranslation'; + +import { + getComponentTitle, + getComponentTranslationKey, +} from '../../translations'; const translations = defineMessages({ courseSettings: { @@ -34,6 +40,7 @@ export const useItemsReloader = (): (() => void) => const SettingsNavigation = (): JSX.Element => { const data = useLoaderData() as CourseAdminItems; + const { t } = useTranslation(); const [items, setItems] = useState(data); @@ -51,14 +58,14 @@ const SettingsNavigation = (): JSX.Element => {
- {items.map(({ title, path }) => ( + {items.map((item) => ( navigate(path)} - variant={path === pathname ? 'filled' : 'outlined'} + key={item.path} + className={`m-2 ${item.path === pathname && 'p-[1px]'}`} + clickable={item.path !== pathname} + label={getComponentTitle(t, item.id, item.title)} + onClick={(): void => navigate(item.path)} + variant={item.path === pathname ? 'filled' : 'outlined'} /> ))}
@@ -84,7 +91,8 @@ const handle: DataHandle = (match, location) => { title: translations.courseSettings, }, { - title: currentItem?.title, + title: + currentItem?.title ?? getComponentTranslationKey(currentItem?.id), url: currentItem?.path, }, ], diff --git a/client/app/bundles/course/admin/pages/ComponentSettings/ComponentSettingsForm.tsx b/client/app/bundles/course/admin/pages/ComponentSettings/ComponentSettingsForm.tsx index 23435782fb7..075d6126a51 100644 --- a/client/app/bundles/course/admin/pages/ComponentSettings/ComponentSettingsForm.tsx +++ b/client/app/bundles/course/admin/pages/ComponentSettings/ComponentSettingsForm.tsx @@ -3,6 +3,7 @@ import { FormControlLabel, Switch } from '@mui/material'; import { produce } from 'immer'; import { CourseComponents } from 'types/course/admin/components'; +import { getComponentTitle } from 'course/translations'; import Section from 'lib/components/core/layouts/Section'; import useTranslation from 'lib/hooks/useTranslation'; @@ -47,7 +48,8 @@ const ComponentSettingsForm = ( className="mb-0" control={} disabled={props.disabled} - label={component.displayName} + id={`component_${component.id}`} + label={getComponentTitle(t, component.id)} onChange={(_, checked): void => toggleComponent(index, checked)} /> ))} diff --git a/client/app/bundles/course/admin/pages/NotificationSettings/index.jsx b/client/app/bundles/course/admin/pages/NotificationSettings/index.jsx index d9fe9d13bb8..cc1a44d90bf 100644 --- a/client/app/bundles/course/admin/pages/NotificationSettings/index.jsx +++ b/client/app/bundles/course/admin/pages/NotificationSettings/index.jsx @@ -12,7 +12,6 @@ import { } from '@mui/material'; import PropTypes from 'prop-types'; -import adminTranslations from 'course/translations.intl'; import Section from 'lib/components/core/layouts/Section'; import LoadingIndicator from 'lib/components/core/LoadingIndicator'; import messagesTranslations from 'lib/translations/messages'; @@ -106,7 +105,7 @@ class NotificationSettings extends Component { - + diff --git a/client/app/bundles/course/admin/pages/NotificationSettings/translations.intl.js b/client/app/bundles/course/admin/pages/NotificationSettings/translations.intl.js index 81865edc3f9..5eecfd858fc 100644 --- a/client/app/bundles/course/admin/pages/NotificationSettings/translations.intl.js +++ b/client/app/bundles/course/admin/pages/NotificationSettings/translations.intl.js @@ -1,6 +1,10 @@ import { defineMessages } from 'react-intl'; const translations = defineMessages({ + component: { + id: 'course.admin.NotificationSettings.component', + defaultMessage: 'Component', + }, setting: { id: 'course.admin.NotificationSettings.setting', defaultMessage: 'Setting', diff --git a/client/app/bundles/course/admin/pages/SidebarSettings/SidebarSettingsForm.tsx b/client/app/bundles/course/admin/pages/SidebarSettings/SidebarSettingsForm.tsx index 3f7124d2569..02c3b12f170 100644 --- a/client/app/bundles/course/admin/pages/SidebarSettings/SidebarSettingsForm.tsx +++ b/client/app/bundles/course/admin/pages/SidebarSettings/SidebarSettingsForm.tsx @@ -18,6 +18,7 @@ import { import { produce } from 'immer'; import { SidebarItem, SidebarItems } from 'types/course/admin/sidebar'; +import { getComponentTitle } from 'course/translations'; import Section from 'lib/components/core/layouts/Section'; import { defensivelyGetIcon } from 'lib/constants/icons'; import useTranslation from 'lib/hooks/useTranslation'; @@ -131,7 +132,7 @@ const SidebarSettingsForm = (props: SidebarSettingsFormProps): JSX.Element => { color={props.disabled ? 'text.disabled' : 'text.primary'} variant="body2" > - {item.title} + {getComponentTitle(t, item.id, item.title)} diff --git a/client/app/bundles/course/container/Sidebar/SidebarItem.tsx b/client/app/bundles/course/container/Sidebar/SidebarItem.tsx index ea8edc51deb..2c96ac40899 100644 --- a/client/app/bundles/course/container/Sidebar/SidebarItem.tsx +++ b/client/app/bundles/course/container/Sidebar/SidebarItem.tsx @@ -1,5 +1,4 @@ import { useLayoutEffect, useRef } from 'react'; -import { defineMessages } from 'react-intl'; import { Link, useLocation } from 'react-router-dom'; import { Badge, Typography } from '@mui/material'; import { SidebarItemData } from 'types/course/courses'; @@ -8,6 +7,8 @@ import { defensivelyGetIcon } from 'lib/constants/icons'; import { useUnreadCountForItem } from 'lib/hooks/unread'; import useTranslation from 'lib/hooks/useTranslation'; +import { getComponentTitle } from '../../translations'; + interface SidebarItemProps { of: SidebarItemData; square?: boolean; @@ -18,6 +19,8 @@ interface SidebarItemProps { const SidebarItem = (props: SidebarItemProps): JSX.Element => { const { of: item, square, exact, activePath } = props; + const { t } = useTranslation(); + const location = useLocation(); const activeUrl = activePath ?? location.pathname + location.search; @@ -42,6 +45,7 @@ const SidebarItem = (props: SidebarItemProps): JSX.Element => {
{ className="overflow-hidden text-ellipsis whitespace-nowrap font-medium" variant="body2" > - {item.label} + {getComponentTitle(t, item.key, item.label)}
); }; -const translations = defineMessages({ - home: { - id: 'course.courses.SidebarItem.home', - defaultMessage: 'Home', - }, -}); - const HomeSidebarItem = (props: { to: string }): JSX.Element => { - const { t } = useTranslation(); - return ( diff --git a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AchievementsListing.jsx b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AchievementsListing.jsx index f8ead04dd96..0ca46922721 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AchievementsListing.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AchievementsListing.jsx @@ -15,7 +15,7 @@ import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon'; import { duplicableItemTypes } from 'course/duplication/constants'; import { achievementShape } from 'course/duplication/propTypes'; import { getAchievementBadgeUrl } from 'course/helper/achievements'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const styles = { badge: { @@ -73,7 +73,7 @@ class AchievementsListing extends Component { <> diff --git a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AssessmentsListing.jsx b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AssessmentsListing.jsx index b62ccda9ae6..d649864dfa8 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AssessmentsListing.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/AssessmentsListing.jsx @@ -9,7 +9,7 @@ import TypeBadge from 'course/duplication/components/TypeBadge'; import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon'; import { duplicableItemTypes } from 'course/duplication/constants'; import { categoryShape } from 'course/duplication/propTypes'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const { TAB, ASSESSMENT, CATEGORY } = duplicableItemTypes; @@ -177,7 +177,7 @@ class AssessmentsListing extends Component { <> {categoriesTrees.map((category) => diff --git a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/MaterialsListing.jsx b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/MaterialsListing.jsx index 8b14b8fb434..557f29e94a2 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/MaterialsListing.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/MaterialsListing.jsx @@ -8,7 +8,7 @@ import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox'; import TypeBadge from 'course/duplication/components/TypeBadge'; import { duplicableItemTypes } from 'course/duplication/constants'; import { folderShape } from 'course/duplication/propTypes'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const { FOLDER, MATERIAL } = duplicableItemTypes; const ROOT_CHILDREN_LEVEL = 1; @@ -112,7 +112,7 @@ class MaterialsListing extends Component {
diff --git a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/SurveyListing.jsx b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/SurveyListing.jsx index d0ba5976d35..d41acd77c26 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/SurveyListing.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/SurveyListing.jsx @@ -14,7 +14,7 @@ import TypeBadge from 'course/duplication/components/TypeBadge'; import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon'; import { duplicableItemTypes } from 'course/duplication/constants'; import { surveyShape } from 'course/duplication/propTypes'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const styles = { row: { @@ -61,7 +61,7 @@ class SurveyListing extends Component { <> diff --git a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/VideosListing.jsx b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/VideosListing.jsx index ca4ded31899..5963a9a04ee 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/VideosListing.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/DuplicateItemsConfirmation/VideosListing.jsx @@ -9,7 +9,7 @@ import TypeBadge from 'course/duplication/components/TypeBadge'; import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon'; import { duplicableItemTypes } from 'course/duplication/constants'; import { videoTabShape } from 'course/duplication/propTypes'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const { VIDEO_TAB, VIDEO } = duplicableItemTypes; @@ -102,7 +102,7 @@ class VideoListing extends Component { <> diff --git a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AchievementsSelector.jsx b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AchievementsSelector.jsx index 749a7e1c45b..977e1394dc0 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AchievementsSelector.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AchievementsSelector.jsx @@ -12,7 +12,7 @@ import { duplicableItemTypes } from 'course/duplication/constants'; import { achievementShape } from 'course/duplication/propTypes'; import { actions } from 'course/duplication/store'; import { getAchievementBadgeUrl } from 'course/helper/achievements'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; import Thumbnail from 'lib/components/core/Thumbnail'; const translations = defineMessages({ @@ -118,7 +118,7 @@ class AchievementsSelector extends Component { <> {this.renderBody()} diff --git a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AssessmentsSelector.jsx b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AssessmentsSelector.jsx index 88569dd5bcd..fb5c7afed50 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AssessmentsSelector.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/AssessmentsSelector.jsx @@ -12,7 +12,7 @@ import { duplicableItemTypes } from 'course/duplication/constants'; import { categoryShape } from 'course/duplication/propTypes'; import destinationCourseSelector from 'course/duplication/selectors/destinationCourse'; import { actions } from 'course/duplication/store'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const { TAB, ASSESSMENT, CATEGORY } = duplicableItemTypes; @@ -134,7 +134,7 @@ class AssessmentsSelector extends Component { <> {categories.length > 0 ? ( diff --git a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/MaterialsSelector.jsx b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/MaterialsSelector.jsx index 9102d109235..227a946ca80 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/MaterialsSelector.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/MaterialsSelector.jsx @@ -10,7 +10,7 @@ import TypeBadge from 'course/duplication/components/TypeBadge'; import { duplicableItemTypes } from 'course/duplication/constants'; import { folderShape } from 'course/duplication/propTypes'; import { actions } from 'course/duplication/store'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const { FOLDER, MATERIAL } = duplicableItemTypes; @@ -101,7 +101,7 @@ class MaterialsSelector extends Component { <> {folders.length > 0 ? ( diff --git a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/SurveysSelector.jsx b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/SurveysSelector.jsx index 13bfa68588d..0127124c92d 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/SurveysSelector.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/SurveysSelector.jsx @@ -11,7 +11,7 @@ import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon'; import { duplicableItemTypes } from 'course/duplication/constants'; import { surveyShape } from 'course/duplication/propTypes'; import { actions } from 'course/duplication/store'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const translations = defineMessages({ noItems: { @@ -98,7 +98,7 @@ class SurveysSelector extends Component { <> {this.renderBody()} diff --git a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/VideosSelector.jsx b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/VideosSelector.jsx index 2434498444a..c18fdc34637 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/VideosSelector.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelector/VideosSelector.jsx @@ -11,7 +11,7 @@ import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon'; import { duplicableItemTypes } from 'course/duplication/constants'; import { videoTabShape } from 'course/duplication/propTypes'; import { actions } from 'course/duplication/store'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; const { VIDEO_TAB, VIDEO } = duplicableItemTypes; @@ -120,7 +120,7 @@ class VideosSelector extends Component { <> {this.renderBody()} diff --git a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelectorMenu/index.jsx b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelectorMenu/index.jsx index 278ea18b68a..a91172df79c 100644 --- a/client/app/bundles/course/duplication/pages/Duplication/ItemsSelectorMenu/index.jsx +++ b/client/app/bundles/course/duplication/pages/Duplication/ItemsSelectorMenu/index.jsx @@ -17,7 +17,7 @@ import { } from 'course/duplication/constants'; import { courseShape } from 'course/duplication/propTypes'; import { actions } from 'course/duplication/store'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; import DuplicateButton from '../DuplicateButton'; @@ -72,7 +72,7 @@ class ItemsSelectorMenu extends Component { - + ); diff --git a/client/app/bundles/course/level/pages/LevelsIndex/index.tsx b/client/app/bundles/course/level/pages/LevelsIndex/index.tsx index 0863cddcc87..0b6280039b7 100644 --- a/client/app/bundles/course/level/pages/LevelsIndex/index.tsx +++ b/client/app/bundles/course/level/pages/LevelsIndex/index.tsx @@ -1,7 +1,7 @@ import { defineMessages } from 'react-intl'; import { Skeleton, Stack, Typography } from '@mui/material'; -import { defaultComponentTitles } from 'course/translations.intl'; +import componentTranslations from 'course/translations'; import Page from 'lib/components/core/layouts/Page'; import Preload from 'lib/components/wrappers/Preload'; import { useAppDispatch } from 'lib/hooks/store'; @@ -37,7 +37,7 @@ const LevelsIndex = (): JSX.Element => { - {t(defaultComponentTitles.course_levels_component)} + {t(componentTranslations.course_levels_component)} } > diff --git a/client/app/bundles/course/translations.intl.js b/client/app/bundles/course/translations.intl.js deleted file mode 100644 index 43b42246b84..00000000000 --- a/client/app/bundles/course/translations.intl.js +++ /dev/null @@ -1,49 +0,0 @@ -import { defineMessages } from 'react-intl'; - -const translations = defineMessages({ - component: { - id: 'course.component', - defaultMessage: 'Component', - }, -}); - -export const defaultComponentTitles = defineMessages({ - course_achievements_component: { - id: 'course.componentTitles.course_achievements_component', - defaultMessage: 'Achievements', - }, - course_assessments_component: { - id: 'course.componentTitles.course_assessments_component', - defaultMessage: 'Assessments', - }, - course_announcements_component: { - id: 'course.componentTitles.course_announcements_component', - defaultMessage: 'Announcements', - }, - course_survey_component: { - id: 'course.componentTitles.course_survey_component', - defaultMessage: 'Surveys', - }, - course_users_component: { - id: 'course.componentTitles.course_users_component', - defaultMessage: 'Users', - }, - course_forums_component: { - id: 'course.componentTitles.course_forums_component', - defaultMessage: 'Forums', - }, - course_videos_component: { - id: 'course.componentTitles.course_videos_component', - defaultMessage: 'Videos', - }, - course_materials_component: { - id: 'course.componentTitles.course_materials_component', - defaultMessage: 'Materials', - }, - course_levels_component: { - id: 'course.componentTitles.course_levels_component', - defaultMessage: 'Levels', - }, -}); - -export default translations; diff --git a/client/app/bundles/course/translations.ts b/client/app/bundles/course/translations.ts new file mode 100644 index 00000000000..74ee28e438f --- /dev/null +++ b/client/app/bundles/course/translations.ts @@ -0,0 +1,219 @@ +import { defineMessages, MessageDescriptor } from 'react-intl'; + +import { MessageTranslator } from 'lib/hooks/useTranslation'; + +const translations = defineMessages({ + admin_duplication: { + id: 'course.courses.SidebarItem.admin.duplication', + defaultMessage: 'Duplicate Data', + }, + admin_multiple_reference_timelines: { + id: 'course.courses.SidebarItem.admin.multipleReferenceTimelines', + defaultMessage: 'Timeline Designer', + }, + admin_plagiarism: { + id: 'course.courses.SidebarItem.admin.plagiarism', + defaultMessage: 'Plagiarism Check', + }, + admin_scholaistic_assistants: { + id: 'course.courses.SidebarItem.admin.scholaistic.assistants', + defaultMessage: 'Assistants', + }, + admin_settings: { + id: 'course.courses.SidebarItem.admin.settings', + defaultMessage: 'Course Settings', + }, + admin_settings_component_settings: { + id: 'course.courses.SidebarItem.admin.settings.components', + defaultMessage: 'Components', + }, + admin_settings_general: { + id: 'course.courses.SidebarItem.admin.settings.general', + defaultMessage: 'General', + }, + admin_settings_notifications: { + id: 'course.courses.SidebarItem.admin.settings.notifications', + defaultMessage: 'Email', + }, + admin_settings_sidebar_settings: { + id: 'course.courses.SidebarItem.admin.settings.sidebar', + defaultMessage: 'Sidebar', + }, + admin_users_manage_users: { + id: 'course.courses.SidebarItem.admin.users.manageUsers', + defaultMessage: 'Manage Users', + }, + course_achievements_component: { + id: 'course.componentTitles.course_achievements_component', + defaultMessage: 'Achievements', + }, + course_announcements_component: { + id: 'course.componentTitles.course_announcements_component', + defaultMessage: 'Announcements', + }, + course_assessments_component: { + id: 'course.componentTitles.course_assessments_component', + defaultMessage: 'Assessments', + }, + course_codaveri_component: { + id: 'course.componentTitles.course_codaveri_component', + defaultMessage: 'Codaveri Evaluation and Feedback', + }, + course_discussion_topics_component: { + id: 'course.componentTitles.course_discussion_topics_component', + defaultMessage: 'Comments Center', + }, + course_duplication_component: { + id: 'course.componentTitles.course_duplication_component', + defaultMessage: 'Duplication', + }, + course_experience_points_component: { + id: 'course.componentTitles.course_experience_points_component', + defaultMessage: 'Experience Points', + }, + course_forums_component: { + id: 'course.componentTitles.course_forums_component', + defaultMessage: 'Forums', + }, + course_groups_component: { + id: 'course.componentTitles.course_groups_component', + defaultMessage: 'Groups', + }, + course_koditsu_platform_component: { + id: 'course.componentTitles.course_koditsu_platform_component', + defaultMessage: 'Koditsu Exam', + }, + course_leaderboard_component: { + id: 'course.componentTitles.course_leaderboard_component', + defaultMessage: 'Leaderboard', + }, + course_learning_map_component: { + id: 'course.componentTitles.course_learning_map_component', + defaultMessage: 'Learning Map', + }, + course_lesson_plan_component: { + id: 'course.componentTitles.course_lesson_plan_component', + defaultMessage: 'Lesson Plan', + }, + course_levels_component: { + id: 'course.componentTitles.course_levels_component', + defaultMessage: 'Levels', + }, + course_materials_component: { + id: 'course.componentTitles.course_materials_component', + defaultMessage: 'Materials', + }, + course_monitoring_component: { + id: 'course.componentTitles.course_monitoring_component', + defaultMessage: 'Heartbeat Monitoring for Exams', + }, + course_multiple_reference_timelines_component: { + id: 'course.componentTitles.course_multiple_reference_timelines_component', + defaultMessage: 'Multiple Reference Timelines', + }, + course_plagiarism_component: { + id: 'course.componentTitles.course_plagiarism_component', + defaultMessage: 'SSID Plagiarism Check', + }, + course_rag_wise_component: { + id: 'course.componentTitles.course_rag_wise_component', + defaultMessage: 'RagWise Auto Forum Response', + }, + course_scholaistic_component: { + id: 'course.componentTitles.course_scholaistic_component', + defaultMessage: 'Role-Playing Chatbots & Assessments', + }, + course_settings_component: { + id: 'course.componentTitles.course_settings_component', + defaultMessage: 'Settings', + }, + course_statistics_component: { + id: 'course.componentTitles.course_statistics_component', + defaultMessage: 'Statistics', + }, + course_stories_component: { + id: 'course.componentTitles.course_stories_component', + defaultMessage: 'Stories', + }, + course_survey_component: { + id: 'course.componentTitles.course_survey_component', + defaultMessage: 'Surveys', + }, + course_users_component: { + id: 'course.componentTitles.course_users_component', + defaultMessage: 'Users', + }, + course_videos_component: { + id: 'course.componentTitles.course_videos_component', + defaultMessage: 'Videos', + }, + sidebar_assessments_skills: { + id: 'course.courses.SidebarItem.assessmentSkills', + defaultMessage: 'Skills', + }, + sidebar_assessments_submissions: { + id: 'course.courses.SidebarItem.assessmentSubmissions', + defaultMessage: 'Submissions', + }, + sidebar_discussion_topics: { + id: 'course.courses.SidebarItem.discussionTopics', + defaultMessage: 'Comments', + }, + sidebar_experience_points: { + id: 'course.courses.SidebarItem.experiencePoints', + defaultMessage: 'Experience Points', + }, + sidebar_home: { + id: 'course.courses.SidebarItem.home', + defaultMessage: 'Home', + }, + sidebar_stories_learn: { + id: 'course.courses.SidebarItem.stories.learn', + defaultMessage: 'Learn', + }, + sidebar_stories_mission_control: { + id: 'course.courses.SidebarItem.stories.missionControl', + defaultMessage: 'Mission Control', + }, +}); + +// Keys for main sidebar items (the ones students can see) cannot be changed, +// because the ordering of sidebar items for each course is saved in DB using these names. +const LegacySidebarItemKeyMapper: Record = { + announcements: translations.course_announcements_component, + achievements: translations.course_achievements_component, + assessments_submissions: translations.sidebar_assessments_submissions, + discussion_topics: translations.sidebar_discussion_topics, + forums: translations.course_forums_component, + leaderboard: translations.course_leaderboard_component, + learning_map: translations.course_learning_map_component, + lesson_plan: translations.course_lesson_plan_component, + materials: translations.course_materials_component, + learn: translations.sidebar_stories_learn, + surveys: translations.course_survey_component, + users: translations.course_users_component, + videos: translations.course_videos_component, +}; + +export const getComponentTranslationKey = ( + key?: string, +): MessageDescriptor | undefined => { + if (!key) return undefined; + if (translations[key]) return translations[key]; + if (LegacySidebarItemKeyMapper[key]) return LegacySidebarItemKeyMapper[key]; + return undefined; +}; + +export const getComponentTitle = ( + t: MessageTranslator, + key?: string, + definedTitle?: string, +): string | undefined => { + if (definedTitle && definedTitle.length > 0) return definedTitle; + + const translationKey = getComponentTranslationKey(key); + if (translationKey) return t(translationKey); + return key; +}; + +export default translations; diff --git a/client/app/bundles/system/admin/instance/instance/pages/InstanceComponentsIndex.tsx b/client/app/bundles/system/admin/instance/instance/pages/InstanceComponentsIndex.tsx index 6c1c65c2b7e..f16b84b5e29 100644 --- a/client/app/bundles/system/admin/instance/instance/pages/InstanceComponentsIndex.tsx +++ b/client/app/bundles/system/admin/instance/instance/pages/InstanceComponentsIndex.tsx @@ -1,5 +1,5 @@ import { FC, useEffect, useState } from 'react'; -import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { Switch, Table, @@ -10,16 +10,16 @@ import { } from '@mui/material'; import { ComponentData } from 'types/system/instance/components'; +import { getComponentTitle } from 'course/translations'; import LoadingIndicator from 'lib/components/core/LoadingIndicator'; import { useAppDispatch } from 'lib/hooks/store'; import toast from 'lib/hooks/toast'; +import useTranslation from 'lib/hooks/useTranslation'; import tableTranslations from 'lib/translations/table'; import { indexComponents, updateComponents } from '../operations'; import { actions } from '../store'; -type Props = WrappedComponentProps; - const translations = defineMessages({ fetchComponentsFailure: { id: 'system.admin.instance.instance.InstanceComponentsForm.fetchComponentsFailure', @@ -35,8 +35,8 @@ const translations = defineMessages({ }, }); -const InstanceComponentsIndex: FC = (props) => { - const { intl } = props; +const InstanceComponentsIndex: FC = () => { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(false); const [isUpdating, setIsUpdating] = useState(false); @@ -49,9 +49,7 @@ const InstanceComponentsIndex: FC = (props) => { setComponents(data); dispatch(actions.initComponentList(data)); }) - .catch(() => - toast.error(intl.formatMessage(translations.fetchComponentsFailure)), - ) + .catch(() => toast.error(t(translations.fetchComponentsFailure))) .finally(() => { setIsLoading(false); }); @@ -63,11 +61,9 @@ const InstanceComponentsIndex: FC = (props) => { .then((data) => { setComponents(data); dispatch(actions.initComponentList(data)); - toast.success(intl.formatMessage(translations.updateComponentsSuccess)); + toast.success(t(translations.updateComponentsSuccess)); }) - .catch(() => - toast.error(intl.formatMessage(translations.updateComponentsFailed)), - ) + .catch(() => toast.error(t(translations.updateComponentsFailed))) .finally(() => setIsUpdating(false)); }; @@ -77,18 +73,14 @@ const InstanceComponentsIndex: FC = (props) => { - - {intl.formatMessage(tableTranslations.component)} - - - {intl.formatMessage(tableTranslations.isEnabled)} - + {t(tableTranslations.component)} + {t(tableTranslations.isEnabled)} {components.map((component) => ( - {component.displayName} + {getComponentTitle(t, component.key)} = (props) => { ); }; -export default injectIntl(InstanceComponentsIndex); +export default InstanceComponentsIndex; diff --git a/client/app/types/course/admin/components.ts b/client/app/types/course/admin/components.ts index 443ec6289c8..8b31bc8da91 100644 --- a/client/app/types/course/admin/components.ts +++ b/client/app/types/course/admin/components.ts @@ -1,6 +1,5 @@ export interface CourseComponent { id: string; - displayName: string; enabled: boolean; } diff --git a/client/app/types/course/admin/course.ts b/client/app/types/course/admin/course.ts index 6a6babb2f39..6990c234018 100644 --- a/client/app/types/course/admin/course.ts +++ b/client/app/types/course/admin/course.ts @@ -15,7 +15,8 @@ export interface CourseInfo { } export interface CourseAdminItem { - title: string; + id: string; + title?: string; weight: number; path: string; } diff --git a/client/app/types/course/admin/sidebar.ts b/client/app/types/course/admin/sidebar.ts index e39479d4ecc..0d1aa8711cd 100644 --- a/client/app/types/course/admin/sidebar.ts +++ b/client/app/types/course/admin/sidebar.ts @@ -2,7 +2,7 @@ import { CourseComponentIconName } from 'lib/constants/icons'; export interface SidebarItem { id: string; - title: string; + title?: string; weight: number; icon: CourseComponentIconName; } diff --git a/client/app/types/course/courses.ts b/client/app/types/course/courses.ts index 25503a9fc8b..083add24cc3 100644 --- a/client/app/types/course/courses.ts +++ b/client/app/types/course/courses.ts @@ -77,7 +77,7 @@ export interface NewCourseFormData { export interface SidebarItemData { key: string; - label: string; + label?: string; path: string; icon: CourseComponentIconName; unread?: number; diff --git a/client/app/types/system/instance/components.ts b/client/app/types/system/instance/components.ts index 874feb716d7..041024d9094 100644 --- a/client/app/types/system/instance/components.ts +++ b/client/app/types/system/instance/components.ts @@ -1,5 +1,4 @@ export interface ComponentData { key: string; - displayName: string; enabled: boolean; } diff --git a/client/locales/en.json b/client/locales/en.json index 52b28396ce6..a7185a5aaa7 100644 --- a/client/locales/en.json +++ b/client/locales/en.json @@ -4026,7 +4026,7 @@ "course.asssessment.submission.submitNoQuestionExplain": { "defaultMessage": "Mark as completed?" }, - "course.component": { + "course.admin.NotificationSettings.component": { "defaultMessage": "Component" }, "course.componentTitles.course_achievements_component": { @@ -4038,15 +4038,66 @@ "course.componentTitles.course_assessments_component": { "defaultMessage": "Assessments" }, + "course.componentTitles.course_codaveri_component": { + "defaultMessage": "Codaveri Evaluation and Feedback" + }, + "course.componentTitles.course_discussion_topics_component": { + "defaultMessage": "Comments Center" + }, + "course.componentTitles.course_duplication_component": { + "defaultMessage": "Duplication" + }, + "course.componentTitles.course_experience_points_component": { + "defaultMessage": "Experience Points" + }, "course.componentTitles.course_forums_component": { "defaultMessage": "Forums" }, + "course.componentTitles.course_groups_component": { + "defaultMessage": "Groups" + }, + "course.componentTitles.course_koditsu_platform_component": { + "defaultMessage": "Koditsu Exam" + }, + "course.componentTitles.course_leaderboard_component": { + "defaultMessage": "Leaderboard" + }, + "course.componentTitles.course_learning_map_component": { + "defaultMessage": "Learning Map" + }, + "course.componentTitles.course_lesson_plan_component": { + "defaultMessage": "Lesson Plan" + }, "course.componentTitles.course_levels_component": { "defaultMessage": "Levels" }, "course.componentTitles.course_materials_component": { "defaultMessage": "Materials" }, + "course.componentTitles.course_monitoring_component": { + "defaultMessage": "Heartbeat Monitoring for Exams" + }, + "course.componentTitles.course_multiple_reference_timelines_component": { + "defaultMessage": "Multiple Reference Timelines" + }, + "course.componentTitles.course_plagiarism_component": { + "defaultMessage": "SSID Plagiarism Check" + }, + "course.componentTitles.course_rag_wise_component": { + "defaultMessage": "RagWise Auto Forum Response" + }, + "course.componentTitles.course_scholaistic_component": { + "defaultMessage": "Role-Playing Chatbots & Assessments" + }, + "course.componentTitles.course_settings_component": { + "defaultMessage": "Settings" + }, + "course.componentTitles.course_statistics_component": { + "defaultMessage": "Statistics" + }, + "course.componentTitles.course_stories_component": { + "defaultMessage": "Stories" + }, "course.componentTitles.course_survey_component": { "defaultMessage": "Surveys" }, @@ -4227,9 +4278,57 @@ "course.courses.Sidebar.administration": { "defaultMessage": "Administration" }, + "course.courses.SidebarItem.admin.duplication": { + "defaultMessage": "Duplicate Data" + }, + "course.courses.SidebarItem.admin.multipleReferenceTimelines": { + "defaultMessage": "Timeline Designer" + }, + "course.courses.SidebarItem.admin.plagiarism": { + "defaultMessage": "Plagiarism Check" + }, + "course.courses.SidebarItem.admin.scholaistic.assistants": { + "defaultMessage": "Assistants" + }, + "course.courses.SidebarItem.admin.settings": { + "defaultMessage": "Course Settings" + }, + "course.courses.SidebarItem.admin.settings.components": { + "defaultMessage": "Components" + }, + "course.courses.SidebarItem.admin.settings.general": { + "defaultMessage": "General" + }, + "course.courses.SidebarItem.admin.settings.notifications": { + "defaultMessage": "Email" + }, + "course.courses.SidebarItem.admin.settings.sidebar": { + "defaultMessage": "Sidebar" + }, + "course.courses.SidebarItem.admin.users.manageUsers": { + "defaultMessage": "Manage Users" + }, + "course.courses.SidebarItem.assessmentSkills": { + "defaultMessage": "Skills" + }, + "course.courses.SidebarItem.assessmentSubmissions": { + "defaultMessage": "Submissions" + }, + "course.courses.SidebarItem.discussionTopics": { + "defaultMessage": "Comments" + }, + "course.courses.SidebarItem.experiencePoints": { + "defaultMessage": "Experience Points" + }, "course.courses.SidebarItem.home": { "defaultMessage": "Home" }, + "course.courses.SidebarItem.stories.learn": { + "defaultMessage": "Learn" + }, + "course.courses.SidebarItem.stories.missionControl": { + "defaultMessage": "Mission Control" + }, "course.courses.TodoIgnoreButton.ignore.ignoreButtonText": { "defaultMessage": "Ignore" }, diff --git a/client/locales/ko.json b/client/locales/ko.json index e2c59931ba1..88bbe031e26 100644 --- a/client/locales/ko.json +++ b/client/locales/ko.json @@ -4007,7 +4007,7 @@ "course.asssessment.submission.submitNoQuestionExplain": { "defaultMessage": "완료로 표시하시겠습니까?" }, - "course.component": { + "course.admin.NotificationSettings.component": { "defaultMessage": "구성 요소" }, "course.componentTitles.course_achievements_component": { @@ -4019,15 +4019,66 @@ "course.componentTitles.course_assessments_component": { "defaultMessage": "평가" }, + "course.componentTitles.course_codaveri_component": { + "defaultMessage": "Codaveri 평가 및 피드백" + }, + "course.componentTitles.course_discussion_topics_component": { + "defaultMessage": "댓글 센터" + }, + "course.componentTitles.course_duplication_component": { + "defaultMessage": "복제" + }, + "course.componentTitles.course_experience_points_component": { + "defaultMessage": "경험치" + }, "course.componentTitles.course_forums_component": { "defaultMessage": "포럼" }, + "course.componentTitles.course_groups_component": { + "defaultMessage": "그룹" + }, + "course.componentTitles.course_koditsu_platform_component": { + "defaultMessage": "Koditsu 시험" + }, + "course.componentTitles.course_leaderboard_component": { + "defaultMessage": "리더보드" + }, + "course.componentTitles.course_learning_map_component": { + "defaultMessage": "학습 지도" + }, + "course.componentTitles.course_lesson_plan_component": { + "defaultMessage": "수업 계획" + }, "course.componentTitles.course_levels_component": { "defaultMessage": "레벨" }, "course.componentTitles.course_materials_component": { "defaultMessage": "자료" }, + "course.componentTitles.course_monitoring_component": { + "defaultMessage": "시험 모니터링" + }, + "course.componentTitles.course_multiple_reference_timelines_component": { + "defaultMessage": "다중 참조 타임라인" + }, + "course.componentTitles.course_plagiarism_component": { + "defaultMessage": "SSID 표절 검사" + }, + "course.componentTitles.course_rag_wise_component": { + "defaultMessage": "RagWise 자동 포럼 응답" + }, + "course.componentTitles.course_scholaistic_component": { + "defaultMessage": "롤플레잉 챗봇 및 평가" + }, + "course.componentTitles.course_settings_component": { + "defaultMessage": "설정" + }, + "course.componentTitles.course_statistics_component": { + "defaultMessage": "통계" + }, + "course.componentTitles.course_stories_component": { + "defaultMessage": "이야기" + }, "course.componentTitles.course_survey_component": { "defaultMessage": "설문조사" }, @@ -4208,9 +4259,57 @@ "course.courses.Sidebar.administration": { "defaultMessage": "관리" }, + "course.courses.SidebarItem.admin.duplication": { + "defaultMessage": "데이터 복제" + }, + "course.courses.SidebarItem.admin.multipleReferenceTimelines": { + "defaultMessage": "타임라인 디자이너" + }, + "course.courses.SidebarItem.admin.plagiarism": { + "defaultMessage": "SSID 표절 검사" + }, + "course.courses.SidebarItem.admin.scholaistic.assistants": { + "defaultMessage": "학습 조수" + }, + "course.courses.SidebarItem.admin.settings": { + "defaultMessage": "코스 설정" + }, + "course.courses.SidebarItem.admin.settings.components": { + "defaultMessage": "구성 요소" + }, + "course.courses.SidebarItem.admin.settings.general": { + "defaultMessage": "일반" + }, + "course.courses.SidebarItem.admin.settings.notifications": { + "defaultMessage": "이메일" + }, + "course.courses.SidebarItem.admin.settings.sidebar": { + "defaultMessage": "사이드바" + }, + "course.courses.SidebarItem.admin.users.manageUsers": { + "defaultMessage": "사용자 관리" + }, + "course.courses.SidebarItem.assessmentSkills": { + "defaultMessage": "기술" + }, + "course.courses.SidebarItem.assessmentSubmissions": { + "defaultMessage": "제출물" + }, + "course.courses.SidebarItem.discussionTopics": { + "defaultMessage": "댓글 센터" + }, + "course.courses.SidebarItem.experiencePoints": { + "defaultMessage": "경험치" + }, "course.courses.SidebarItem.home": { "defaultMessage": "홈" }, + "course.courses.SidebarItem.stories.learn": { + "defaultMessage": "배우기" + }, + "course.courses.SidebarItem.stories.missionControl": { + "defaultMessage": "미션 컨트롤" + }, "course.courses.TodoIgnoreButton.ignore.ignoreButtonText": { "defaultMessage": "무시" }, diff --git a/client/locales/zh.json b/client/locales/zh.json index 08846a7363e..064501d8acd 100644 --- a/client/locales/zh.json +++ b/client/locales/zh.json @@ -4001,7 +4001,7 @@ "course.asssessment.submission.submitNoQuestionExplain": { "defaultMessage": "标记为完成?" }, - "course.component": { + "course.admin.NotificationSettings.component": { "defaultMessage": "组件" }, "course.componentTitles.course_achievements_component": { @@ -4013,15 +4013,66 @@ "course.componentTitles.course_assessments_component": { "defaultMessage": "测验" }, + "course.componentTitles.course_codaveri_component": { + "defaultMessage": "Codaveri 评估和反馈" + }, + "course.componentTitles.course_discussion_topics_component": { + "defaultMessage": "评论中心" + }, + "course.componentTitles.course_duplication_component": { + "defaultMessage": "复制" + }, + "course.componentTitles.course_experience_points_component": { + "defaultMessage": "经验值" + }, "course.componentTitles.course_forums_component": { "defaultMessage": "论坛" }, + "course.componentTitles.course_groups_component": { + "defaultMessage": "组" + }, + "course.componentTitles.course_koditsu_platform_component": { + "defaultMessage": "Koditsu 考试" + }, + "course.componentTitles.course_leaderboard_component": { + "defaultMessage": "排行榜" + }, + "course.componentTitles.course_learning_map_component": { + "defaultMessage": "学习路线" + }, + "course.componentTitles.course_lesson_plan_component": { + "defaultMessage": "课程计划" + }, "course.componentTitles.course_levels_component": { "defaultMessage": "等级" }, "course.componentTitles.course_materials_component": { "defaultMessage": "材料" }, + "course.componentTitles.course_monitoring_component": { + "defaultMessage": "监听" + }, + "course.componentTitles.course_multiple_reference_timelines_component": { + "defaultMessage": "多重参考时间线" + }, + "course.componentTitles.course_plagiarism_component": { + "defaultMessage": "SSID 抄袭检查" + }, + "course.componentTitles.course_rag_wise_component": { + "defaultMessage": "RagWise 自动论坛回复" + }, + "course.componentTitles.course_scholaistic_component": { + "defaultMessage": "角色扮演聊天机器人和评估" + }, + "course.componentTitles.course_settings_component": { + "defaultMessage": "设置" + }, + "course.componentTitles.course_statistics_component": { + "defaultMessage": "统计" + }, + "course.componentTitles.course_stories_component": { + "defaultMessage": "故事" + }, "course.componentTitles.course_survey_component": { "defaultMessage": "调查" }, @@ -4202,9 +4253,57 @@ "course.courses.Sidebar.administration": { "defaultMessage": "课程管理" }, + "course.courses.SidebarItem.admin.duplication": { + "defaultMessage": "复制数据" + }, + "course.courses.SidebarItem.admin.multipleReferenceTimelines": { + "defaultMessage": "时间线设计工具" + }, + "course.courses.SidebarItem.admin.plagiarism": { + "defaultMessage": "SSID 抄袭检查" + }, + "course.courses.SidebarItem.admin.scholaistic.assistants": { + "defaultMessage": "学习助手" + }, + "course.courses.SidebarItem.admin.settings": { + "defaultMessage": "课程设置" + }, + "course.courses.SidebarItem.admin.settings.components": { + "defaultMessage": "功能组件" + }, + "course.courses.SidebarItem.admin.settings.general": { + "defaultMessage": "通用" + }, + "course.courses.SidebarItem.admin.settings.notifications": { + "defaultMessage": "邮箱" + }, + "course.courses.SidebarItem.admin.settings.sidebar": { + "defaultMessage": "侧边栏" + }, + "course.courses.SidebarItem.admin.users.manageUsers": { + "defaultMessage": "管理用户" + }, + "course.courses.SidebarItem.assessmentSkills": { + "defaultMessage": "技能" + }, + "course.courses.SidebarItem.assessmentSubmissions": { + "defaultMessage": "提交" + }, + "course.courses.SidebarItem.discussionTopics": { + "defaultMessage": "评论中心" + }, + "course.courses.SidebarItem.experiencePoints": { + "defaultMessage": "经验值" + }, "course.courses.SidebarItem.home": { "defaultMessage": "主页" }, + "course.courses.SidebarItem.stories.learn": { + "defaultMessage": "学习" + }, + "course.courses.SidebarItem.stories.missionControl": { + "defaultMessage": "任务控制" + }, "course.courses.TodoIgnoreButton.ignore.ignoreButtonText": { "defaultMessage": "忽视" }, From 344f7115bf75868ac30ee7a5e62ce4e1cb4ecf7d Mon Sep 17 00:00:00 2001 From: adi-herwana-nus Date: Mon, 8 Dec 2025 12:54:36 +0800 Subject: [PATCH 2/9] feat(translations): remove remaining references to BE component translation keys - removed BE title references in Timeline Designer - removed BE title references in Lesson Plan view - removed BE title references in Materials view & file download --- .../course/lesson_plan/items_controller.rb | 2 +- app/controllers/course/material/controller.rb | 2 +- .../course/settings/survey_component.rb | 2 +- .../course/settings/videos_component.rb | 2 +- .../folders/breadcrumbs.json.jbuilder | 2 +- .../_reference_timeline.json.jbuilder | 2 +- .../_survey_lesson_plan_item.json.jbuilder | 2 +- .../_video_lesson_plan_item.json.jbuilder | 2 +- .../admin/pages/LessonPlanSettings/index.jsx | 17 +++++++++++--- .../containers/LessonPlanFilter/index.jsx | 3 ++- .../containers/TranslatedItemType.tsx | 22 ++++++++++++++++++ .../pages/LessonPlanEdit/ItemRow/index.jsx | 7 +++++- .../LessonPlanItem/Details/Chips.jsx | 4 +++- .../course/material/folders/handles.ts | 23 +++++++++++++++---- .../components/CreateRenameTimelinePrompt.tsx | 2 +- .../components/DeleteTimelinePrompt.tsx | 4 +++- .../TimelinesOverviewItem.tsx | 6 +++-- .../reference-timelines/translations.ts | 2 +- .../views/DayView/ItemsSidebar.tsx | 5 +++- .../views/DayView/TimelineSidebarItem.tsx | 2 +- client/app/types/course/material/folders.ts | 2 +- client/app/types/course/referenceTimelines.ts | 2 +- 22 files changed, 89 insertions(+), 28 deletions(-) create mode 100644 client/app/bundles/course/lesson-plan/containers/TranslatedItemType.tsx diff --git a/app/controllers/course/lesson_plan/items_controller.rb b/app/controllers/course/lesson_plan/items_controller.rb index 95904a0c5d5..4a5e60b574e 100644 --- a/app/controllers/course/lesson_plan/items_controller.rb +++ b/app/controllers/course/lesson_plan/items_controller.rb @@ -74,7 +74,7 @@ def assessment_tabs_visibility_hash # @return [Hash{Array => Boolean}] def component_visibility_hash @component_visibility_hash = component_item_settings.to_h do |setting| - [[setting[:component_title]], setting[:visible]] + [[setting[:component]], setting[:visible]] end end diff --git a/app/controllers/course/material/controller.rb b/app/controllers/course/material/controller.rb index 638d4aad7c1..41cfbd5fea7 100644 --- a/app/controllers/course/material/controller.rb +++ b/app/controllers/course/material/controller.rb @@ -46,6 +46,6 @@ def component helper_method :component def root_folder_name - component.settings.title || t('course.material.sidebar_title') + component.settings.title || current_course.title end end diff --git a/app/models/course/settings/survey_component.rb b/app/models/course/settings/survey_component.rb index e24fbbf5b5d..8eacc7a69a6 100644 --- a/app/models/course/settings/survey_component.rb +++ b/app/models/course/settings/survey_component.rb @@ -3,7 +3,7 @@ class Course::Settings::SurveyComponent < Course::Settings::Component include Course::Settings::LessonPlanSettingsConcern def lesson_plan_item_settings - super.merge(component_title: I18n.t('components.surveys.name')) + super end def showable_in_lesson_plan? diff --git a/app/models/course/settings/videos_component.rb b/app/models/course/settings/videos_component.rb index 633688da464..e73799dcfc4 100644 --- a/app/models/course/settings/videos_component.rb +++ b/app/models/course/settings/videos_component.rb @@ -8,7 +8,7 @@ def self.component_class end def lesson_plan_item_settings - super.merge(component_title: I18n.t('course.video.videos.index.header')) + super.merge(component_title: title) end def showable_in_lesson_plan? diff --git a/app/views/course/material/folders/breadcrumbs.json.jbuilder b/app/views/course/material/folders/breadcrumbs.json.jbuilder index 93b5aec1254..70514c00415 100644 --- a/app/views/course/material/folders/breadcrumbs.json.jbuilder +++ b/app/views/course/material/folders/breadcrumbs.json.jbuilder @@ -2,5 +2,5 @@ json.breadcrumbs @folder.ancestors.reverse << @folder do |folder| json.id folder.id - json.name folder.parent_id.nil? ? @settings.title || t('course.material.sidebar_title') : folder.name + json.name folder.parent_id.nil? ? @settings.title : folder.name end diff --git a/app/views/course/reference_timelines/_reference_timeline.json.jbuilder b/app/views/course/reference_timelines/_reference_timeline.json.jbuilder index c7d64c36233..fe11336c55d 100644 --- a/app/views/course/reference_timelines/_reference_timeline.json.jbuilder +++ b/app/views/course/reference_timelines/_reference_timeline.json.jbuilder @@ -1,6 +1,6 @@ # frozen_string_literal: true json.id timeline.id -json.title timeline.title || t('.default_title') +json.title timeline.title json.timesCount timeline.reference_times.size json.weight timeline.weight if timeline.weight.present? diff --git a/app/views/course/surveys/_survey_lesson_plan_item.json.jbuilder b/app/views/course/surveys/_survey_lesson_plan_item.json.jbuilder index 75e00d09b95..a5bb0bdffbc 100644 --- a/app/views/course/surveys/_survey_lesson_plan_item.json.jbuilder +++ b/app/views/course/surveys/_survey_lesson_plan_item.json.jbuilder @@ -1,4 +1,4 @@ # frozen_string_literal: true json.partial! 'course/lesson_plan/items/item', item: item -json.lesson_plan_item_type [t('components.surveys.name')] +json.lesson_plan_item_type :course_surveys_component json.item_path course_survey_path(current_course, item) diff --git a/app/views/course/video/videos/_video_lesson_plan_item.json.jbuilder b/app/views/course/video/videos/_video_lesson_plan_item.json.jbuilder index 0753e17aeb1..118fb3d906d 100644 --- a/app/views/course/video/videos/_video_lesson_plan_item.json.jbuilder +++ b/app/views/course/video/videos/_video_lesson_plan_item.json.jbuilder @@ -3,5 +3,5 @@ json.partial! 'course/lesson_plan/items/item', item: item json.item_path course_video_path(current_course, item) json.description format_ckeditor_rich_text(item.description) -type = current_component_host[:course_videos_component]&.settings&.title || t('components.video.name') +type = current_component_host[:course_videos_component]&.settings&.title || :course_videos_component json.lesson_plan_item_type [type] diff --git a/client/app/bundles/course/admin/pages/LessonPlanSettings/index.jsx b/client/app/bundles/course/admin/pages/LessonPlanSettings/index.jsx index 365ba663448..451a3b2271b 100644 --- a/client/app/bundles/course/admin/pages/LessonPlanSettings/index.jsx +++ b/client/app/bundles/course/admin/pages/LessonPlanSettings/index.jsx @@ -14,6 +14,7 @@ import { } from '@mui/material'; import PropTypes from 'prop-types'; +import { getComponentTranslationKey } from 'course/translations'; import Section from 'lib/components/core/layouts/Section'; import Subsection from 'lib/components/core/layouts/Subsection'; import LoadingIndicator from 'lib/components/core/LoadingIndicator'; @@ -56,7 +57,11 @@ class LessonPlanSettings extends Component { options, }, }; - const values = { setting: tab_title || component_title }; + const values = { + setting: tab_title || component_title || ( + + ), + }; const successMessage = ( ); @@ -84,7 +89,11 @@ class LessonPlanSettings extends Component { options, }, }; - const values = { setting: tab_title || component_title }; + const values = { + setting: tab_title || component_title || ( + + ), + }; const successMessage = ( ); @@ -126,7 +135,9 @@ class LessonPlanSettings extends Component { } renderComponentSettingRow(setting) { - const componentTitle = setting.component_title || setting.component; + const componentTitle = setting.component_title || ( + + ); return ( diff --git a/client/app/bundles/course/lesson-plan/containers/LessonPlanFilter/index.jsx b/client/app/bundles/course/lesson-plan/containers/LessonPlanFilter/index.jsx index 059998aabaa..61431219d76 100644 --- a/client/app/bundles/course/lesson-plan/containers/LessonPlanFilter/index.jsx +++ b/client/app/bundles/course/lesson-plan/containers/LessonPlanFilter/index.jsx @@ -7,6 +7,7 @@ import { Button, MenuItem, MenuList, Popover } from '@mui/material'; import PropTypes from 'prop-types'; import { actions } from '../../store'; +import TranslatedItemType from '../TranslatedItemType'; const translations = defineMessages({ filter: { @@ -78,7 +79,7 @@ class LessonPlanFilter extends Component { } style={{ display: 'flex', justifyContent: 'space-between' }} > - {itemType} + {isVisible && } ); diff --git a/client/app/bundles/course/lesson-plan/containers/TranslatedItemType.tsx b/client/app/bundles/course/lesson-plan/containers/TranslatedItemType.tsx new file mode 100644 index 00000000000..6b1be4947dd --- /dev/null +++ b/client/app/bundles/course/lesson-plan/containers/TranslatedItemType.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react'; + +import { getComponentTitle } from 'course/translations'; +import useTranslation from 'lib/hooks/useTranslation'; + +// TODO: properly handle this by separating raw strings & component keys. +const TranslatedItemType: FC<{ type: string }> = ({ type }) => { + const { t } = useTranslation(); + const isTypeComponentKey = [ + 'course_surveys_component', + 'course_videos_component', + ].includes(type); + + return ( + <> + {isTypeComponentKey && getComponentTitle(t, type)} + {!isTypeComponentKey && type} + + ); +}; + +export default TranslatedItemType; diff --git a/client/app/bundles/course/lesson-plan/pages/LessonPlanEdit/ItemRow/index.jsx b/client/app/bundles/course/lesson-plan/pages/LessonPlanEdit/ItemRow/index.jsx index 30e61a95bfc..c8e764e108d 100644 --- a/client/app/bundles/course/lesson-plan/pages/LessonPlanEdit/ItemRow/index.jsx +++ b/client/app/bundles/course/lesson-plan/pages/LessonPlanEdit/ItemRow/index.jsx @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import Link from 'lib/components/core/Link'; import { fields } from '../../../constants'; +import TranslatedItemType from '../../../containers/TranslatedItemType'; import { updateItem } from '../../../operations'; import DateCell from './DateCell'; @@ -68,7 +69,11 @@ class ItemRow extends Component { return ( - {columnsVisible[fields.ITEM_TYPE] ? : null} + {columnsVisible[fields.ITEM_TYPE] ? ( + + ) : null} diff --git a/client/app/bundles/course/lesson-plan/pages/LessonPlanShow/LessonPlanItem/Details/Chips.jsx b/client/app/bundles/course/lesson-plan/pages/LessonPlanShow/LessonPlanItem/Details/Chips.jsx index 64f69595180..bf859481e0e 100644 --- a/client/app/bundles/course/lesson-plan/pages/LessonPlanShow/LessonPlanItem/Details/Chips.jsx +++ b/client/app/bundles/course/lesson-plan/pages/LessonPlanShow/LessonPlanItem/Details/Chips.jsx @@ -10,6 +10,8 @@ import PropTypes from 'prop-types'; import moment, { formatShortDateTime, formatShortTime } from 'lib/moment'; +import TranslatedItemType from '../../../../containers/TranslatedItemType'; + const translations = defineMessages({ notPublished: { id: 'course.lessonPlan.LessonPlanShow.LessonPlanItem.Details.Chip.notPublished', @@ -127,7 +129,7 @@ class Chips extends Component { } - label={itemType} + label={} style={styles.chip} /> ); diff --git a/client/app/bundles/course/material/folders/handles.ts b/client/app/bundles/course/material/folders/handles.ts index aa21b2dac08..b7c4e7f4599 100644 --- a/client/app/bundles/course/material/folders/handles.ts +++ b/client/app/bundles/course/material/folders/handles.ts @@ -4,6 +4,7 @@ import { FolderData } from 'types/course/material/folders'; import { getIdFromUnknown } from 'utilities'; import CourseAPI from 'api/course'; +import componentTranslations from 'course/translations'; import { CrumbPath, DataHandle } from 'lib/hooks/router/dynamicNest'; const translations = defineMessages({ @@ -25,13 +26,25 @@ const translations = defineMessages({ */ const buildCrumbPath = ( courseUrl: string, - breadcrumbs: { id: number; name: string }[], + breadcrumbs: { id: number; name?: string }[], ): CrumbPath => ({ activePath: `${courseUrl}/materials/folders/${breadcrumbs[0].id}`, - content: breadcrumbs.map((crumb) => ({ - title: crumb.id < 0 ? translations.error : crumb.name, - url: `materials/folders/${crumb.id < 0 ? '' : crumb.id}`, - })), + content: breadcrumbs.map((crumb) => { + if (crumb.id < 0) { + return { + title: translations.error, + url: `materials/folders/`, + }; + } + + return { + title: + crumb.name && crumb.name.length > 0 + ? crumb.name + : componentTranslations.course_materials_component, + url: `materials/folders/${crumb.id}`, + }; + }), }); /** diff --git a/client/app/bundles/course/reference-timelines/components/CreateRenameTimelinePrompt.tsx b/client/app/bundles/course/reference-timelines/components/CreateRenameTimelinePrompt.tsx index 29633a34fe8..71aa825a0ea 100644 --- a/client/app/bundles/course/reference-timelines/components/CreateRenameTimelinePrompt.tsx +++ b/client/app/bundles/course/reference-timelines/components/CreateRenameTimelinePrompt.tsx @@ -116,7 +116,7 @@ const CreateRenameTimelinePrompt = ( } title={ timeline - ? t(translations.renameTimelineTitle, { title: timeline.title }) + ? t(translations.renameTimelineTitle, { title: timeline.title ?? '' }) : t(translations.newTimeline) } > diff --git a/client/app/bundles/course/reference-timelines/components/DeleteTimelinePrompt.tsx b/client/app/bundles/course/reference-timelines/components/DeleteTimelinePrompt.tsx index afc5a595a32..ed99202d4bd 100644 --- a/client/app/bundles/course/reference-timelines/components/DeleteTimelinePrompt.tsx +++ b/client/app/bundles/course/reference-timelines/components/DeleteTimelinePrompt.tsx @@ -46,7 +46,9 @@ const DeleteTimelinePrompt = ( ? t(translations.confirmRevertAndDeleteTimeline) : t(translations.confirmDeleteTimeline) } - title={t(translations.sureDeletingTimeline, { title: timeline.title })} + title={t(translations.sureDeletingTimeline, { + title: timeline.title ?? '', + })} > {Boolean(timeline.timesCount) && ( diff --git a/client/app/bundles/course/reference-timelines/components/TimelinesOverview/TimelinesOverviewItem.tsx b/client/app/bundles/course/reference-timelines/components/TimelinesOverview/TimelinesOverviewItem.tsx index 974d74e2b46..ff3c719a084 100644 --- a/client/app/bundles/course/reference-timelines/components/TimelinesOverview/TimelinesOverviewItem.tsx +++ b/client/app/bundles/course/reference-timelines/components/TimelinesOverview/TimelinesOverviewItem.tsx @@ -46,7 +46,9 @@ const TimelinesOverviewItem = ( toast.error( error ?? - t(translations.errorDeletingTimeline, { title: timeline.title }), + t(translations.errorDeletingTimeline, { + title: timeline.title ?? '', + }), ); }); }; @@ -62,7 +64,7 @@ const TimelinesOverviewItem = ( } descriptionVariant="caption" disabled={timeline.default ?? status === 'loading'} - label={timeline.title} + label={timeline.title ?? t(translations.defaultTimeline)} labelClassName="!-mb-2 line-clamp-1 max-w-[20rem]" onChange={(_, checked): void => props.onChangeCheck?.(checked)} variant="body2" diff --git a/client/app/bundles/course/reference-timelines/translations.ts b/client/app/bundles/course/reference-timelines/translations.ts index eaaf61c76fc..6afe68d3c85 100644 --- a/client/app/bundles/course/reference-timelines/translations.ts +++ b/client/app/bundles/course/reference-timelines/translations.ts @@ -158,7 +158,7 @@ export default defineMessages({ }, defaultTimeline: { id: 'course.timelines.defaultTimeline', - defaultMessage: 'Default', + defaultMessage: 'Default Timeline', }, deleteTime: { id: 'course.timelines.deleteTime', diff --git a/client/app/bundles/course/reference-timelines/views/DayView/ItemsSidebar.tsx b/client/app/bundles/course/reference-timelines/views/DayView/ItemsSidebar.tsx index 12081c2be64..a995904f7eb 100644 --- a/client/app/bundles/course/reference-timelines/views/DayView/ItemsSidebar.tsx +++ b/client/app/bundles/course/reference-timelines/views/DayView/ItemsSidebar.tsx @@ -4,8 +4,10 @@ import { TimelineData, } from 'types/course/referenceTimelines'; +import useTranslation from 'lib/hooks/useTranslation'; import moment from 'lib/moment'; +import translations from '../../translations'; import { getDaysFromSeconds } from '../../utils'; import TimelineSidebarItem from './TimelineSidebarItem'; @@ -19,6 +21,7 @@ interface ItemsSidebarProps { const ItemsSidebar = (props: ItemsSidebarProps): JSX.Element => { const { for: items, within: timelines } = props; + const { t } = useTranslation(); return (
{type} + + {title}