mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-06-05 21:04:28 +08:00
fix(client): localize external curriculum structure (#67638)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
94ee73951d
commit
5cd4e8abe6
@ -2,7 +2,7 @@ import path from 'path';
|
||||
import fs, { readFileSync } from 'fs';
|
||||
|
||||
import readdirp from 'readdirp';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { afterEach, describe, test, expect, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
chapterBasedSuperBlocks,
|
||||
@ -10,6 +10,7 @@ import {
|
||||
SuperBlockStage,
|
||||
superBlockStages
|
||||
} from '@freecodecamp/shared/config/curriculum';
|
||||
import { Languages } from '@freecodecamp/shared/config/i18n';
|
||||
import {
|
||||
superblockSchemaValidator,
|
||||
availableSuperBlocksValidator
|
||||
@ -22,7 +23,8 @@ import {
|
||||
type GeneratedChapterBasedCurriculumProps,
|
||||
type ChapterBasedCurriculumIntros,
|
||||
orderedSuperBlockInfo,
|
||||
OrderedSuperBlocks
|
||||
OrderedSuperBlocks,
|
||||
readCurriculumIntros
|
||||
} from './build-external-curricula-data-v2';
|
||||
|
||||
const VERSION = 'v2';
|
||||
@ -34,6 +36,9 @@ const intros = JSON.parse(
|
||||
) as CurriculumIntros;
|
||||
|
||||
describe('external curriculum data build', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
const clientStaticPath = path.resolve(__dirname, '../../../client/static');
|
||||
|
||||
const validateSuperBlock = superblockSchemaValidator();
|
||||
@ -274,13 +279,14 @@ describe('external curriculum data build', () => {
|
||||
next: SuperBlockStage.Next
|
||||
};
|
||||
|
||||
const stages = Object.keys(orderedSuperBlockInfo);
|
||||
const info = orderedSuperBlockInfo();
|
||||
const stages = Object.keys(info);
|
||||
|
||||
expect(stages).not.toContain('next');
|
||||
expect(stages).not.toContain('upcoming');
|
||||
|
||||
for (const stage of stages) {
|
||||
const superBlockDashedNames = orderedSuperBlockInfo[stage]?.map(
|
||||
const superBlockDashedNames = info[stage]?.map(
|
||||
superBlock => superBlock.dashedName
|
||||
);
|
||||
|
||||
@ -306,4 +312,20 @@ describe('external curriculum data build', () => {
|
||||
).length
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('available superblocks should use the configured curriculum locale', () => {
|
||||
vi.stubEnv('CURRICULUM_LOCALE', Languages.Espanol);
|
||||
|
||||
const spanishOrderedSuperBlockInfo = orderedSuperBlockInfo();
|
||||
const spanishIntros = readCurriculumIntros(Languages.Espanol);
|
||||
const englishIntros = readCurriculumIntros(Languages.English);
|
||||
|
||||
expect(spanishOrderedSuperBlockInfo.core[0]).toMatchObject({
|
||||
dashedName: SuperBlocks.RespWebDesignV9,
|
||||
title: spanishIntros[SuperBlocks.RespWebDesignV9].title
|
||||
});
|
||||
expect(spanishOrderedSuperBlockInfo.core[0]?.title).not.toEqual(
|
||||
englishIntros[SuperBlocks.RespWebDesignV9].title
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
SuperBlocks,
|
||||
chapterBasedSuperBlocks
|
||||
} from '@freecodecamp/shared/config/curriculum';
|
||||
import { availableLangs, Languages } from '@freecodecamp/shared/config/i18n';
|
||||
import type { Chapter } from '@freecodecamp/shared/config/chapters';
|
||||
import { getSuperblockStructure } from '@freecodecamp/curriculum/file-handler';
|
||||
import {
|
||||
@ -117,203 +118,218 @@ const ver = 'v2';
|
||||
|
||||
const staticFolderPath = resolve(__dirname, '../../../client/static');
|
||||
const dataPath = `${staticFolderPath}/curriculum-data/`;
|
||||
const blockIntroPath = resolve(
|
||||
__dirname,
|
||||
'../../../client/i18n/locales/english/intro.json'
|
||||
);
|
||||
const intros = JSON.parse(
|
||||
readFileSync(blockIntroPath, 'utf-8')
|
||||
) as CurriculumIntros;
|
||||
const intros = readCurriculumIntros(getCurriculumLocale());
|
||||
|
||||
export const orderedSuperBlockInfo: OrderedSuperBlocks = {
|
||||
[SuperBlockStage.Core]: [
|
||||
{
|
||||
dashedName: SuperBlocks.RespWebDesignV9,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.RespWebDesignV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.JsV9,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.JsV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.PythonV9,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.PythonV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.FrontEndDevLibsV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FrontEndDevLibsV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RelationalDbV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.RelationalDbV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.BackEndDevApisV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.BackEndDevApisV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.FullStackDeveloperV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FullStackDeveloperV9].title
|
||||
}
|
||||
],
|
||||
export function getCurriculumLocale(): Languages {
|
||||
const { CURRICULUM_LOCALE } = process.env;
|
||||
|
||||
[SuperBlockStage.English]: [
|
||||
{
|
||||
dashedName: SuperBlocks.A2English,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.A2English].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.B1English,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.B1English].title
|
||||
}
|
||||
],
|
||||
return availableLangs.curriculum.includes(CURRICULUM_LOCALE as Languages)
|
||||
? (CURRICULUM_LOCALE as Languages)
|
||||
: Languages.English;
|
||||
}
|
||||
|
||||
[SuperBlockStage.Spanish]: [
|
||||
{
|
||||
dashedName: SuperBlocks.A1Spanish,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.A1Spanish].title
|
||||
}
|
||||
],
|
||||
export function readCurriculumIntros(lang: Languages): CurriculumIntros {
|
||||
const blockIntroPath = resolve(
|
||||
__dirname,
|
||||
`../../../client/i18n/locales/${lang}/intro.json`
|
||||
);
|
||||
|
||||
[SuperBlockStage.Chinese]: [
|
||||
{
|
||||
dashedName: SuperBlocks.A1Chinese,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.A1Chinese].title
|
||||
}
|
||||
],
|
||||
return JSON.parse(readFileSync(blockIntroPath, 'utf-8')) as CurriculumIntros;
|
||||
}
|
||||
|
||||
[SuperBlockStage.Extra]: [
|
||||
{
|
||||
dashedName: SuperBlocks.TheOdinProject,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.TheOdinProject].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.CodingInterviewPrep,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.CodingInterviewPrep].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.ProjectEuler,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.ProjectEuler].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RosettaCode,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.RosettaCode].title
|
||||
}
|
||||
],
|
||||
export function orderedSuperBlockInfo(
|
||||
intros: CurriculumIntros = readCurriculumIntros(getCurriculumLocale())
|
||||
): OrderedSuperBlocks {
|
||||
return {
|
||||
[SuperBlockStage.Core]: [
|
||||
{
|
||||
dashedName: SuperBlocks.RespWebDesignV9,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.RespWebDesignV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.JsV9,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.JsV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.PythonV9,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.PythonV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.FrontEndDevLibsV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FrontEndDevLibsV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RelationalDbV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.RelationalDbV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.BackEndDevApisV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.BackEndDevApisV9].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.FullStackDeveloperV9,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FullStackDeveloperV9].title
|
||||
}
|
||||
],
|
||||
|
||||
[SuperBlockStage.Legacy]: [
|
||||
{
|
||||
dashedName: SuperBlocks.RespWebDesignNew,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.RespWebDesignNew].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.JsAlgoDataStructNew,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.JsAlgoDataStructNew].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.FrontEndDevLibs,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FrontEndDevLibs].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.DataVis,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.DataVis].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RelationalDb,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.RelationalDb].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.BackEndDevApis,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.BackEndDevApis].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.QualityAssurance,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.QualityAssurance].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.SciCompPy,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.SciCompPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.DataAnalysisPy,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.DataAnalysisPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.InfoSec,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.InfoSec].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.MachineLearningPy,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.MachineLearningPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.CollegeAlgebraPy,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.CollegeAlgebraPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RespWebDesign,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.RespWebDesign].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.JsAlgoDataStruct,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.JsAlgoDataStruct].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.PythonForEverybody,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.PythonForEverybody].title
|
||||
}
|
||||
],
|
||||
[SuperBlockStage.English]: [
|
||||
{
|
||||
dashedName: SuperBlocks.A2English,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.A2English].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.B1English,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.B1English].title
|
||||
}
|
||||
],
|
||||
|
||||
[SuperBlockStage.Professional]: [
|
||||
{
|
||||
dashedName: SuperBlocks.FoundationalCSharp,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FoundationalCSharp].title
|
||||
}
|
||||
]
|
||||
};
|
||||
[SuperBlockStage.Spanish]: [
|
||||
{
|
||||
dashedName: SuperBlocks.A1Spanish,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.A1Spanish].title
|
||||
}
|
||||
],
|
||||
|
||||
export const superBlockDashedNames = Object.keys(orderedSuperBlockInfo).reduce(
|
||||
(acc, superBlockStage) => {
|
||||
const dashedNames = orderedSuperBlockInfo[superBlockStage].map(
|
||||
[SuperBlockStage.Chinese]: [
|
||||
{
|
||||
dashedName: SuperBlocks.A1Chinese,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.A1Chinese].title
|
||||
}
|
||||
],
|
||||
|
||||
[SuperBlockStage.Extra]: [
|
||||
{
|
||||
dashedName: SuperBlocks.TheOdinProject,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.TheOdinProject].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.CodingInterviewPrep,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.CodingInterviewPrep].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.ProjectEuler,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.ProjectEuler].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RosettaCode,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.RosettaCode].title
|
||||
}
|
||||
],
|
||||
|
||||
[SuperBlockStage.Legacy]: [
|
||||
{
|
||||
dashedName: SuperBlocks.RespWebDesignNew,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.RespWebDesignNew].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.JsAlgoDataStructNew,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.JsAlgoDataStructNew].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.FrontEndDevLibs,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FrontEndDevLibs].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.DataVis,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.DataVis].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RelationalDb,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.RelationalDb].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.BackEndDevApis,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.BackEndDevApis].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.QualityAssurance,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.QualityAssurance].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.SciCompPy,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.SciCompPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.DataAnalysisPy,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.DataAnalysisPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.InfoSec,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.InfoSec].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.MachineLearningPy,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.MachineLearningPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.CollegeAlgebraPy,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.CollegeAlgebraPy].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.RespWebDesign,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.RespWebDesign].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.JsAlgoDataStruct,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.JsAlgoDataStruct].title
|
||||
},
|
||||
{
|
||||
dashedName: SuperBlocks.PythonForEverybody,
|
||||
public: true,
|
||||
title: intros[SuperBlocks.PythonForEverybody].title
|
||||
}
|
||||
],
|
||||
|
||||
[SuperBlockStage.Professional]: [
|
||||
{
|
||||
dashedName: SuperBlocks.FoundationalCSharp,
|
||||
public: false,
|
||||
title: intros[SuperBlocks.FoundationalCSharp].title
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export const superBlockDashedNames = (() => {
|
||||
const info = orderedSuperBlockInfo();
|
||||
return Object.keys(info).reduce((acc, superBlockStage) => {
|
||||
const dashedNames = info[superBlockStage].map(
|
||||
superBlock => superBlock.dashedName
|
||||
);
|
||||
acc.push(...dashedNames);
|
||||
|
||||
return acc;
|
||||
},
|
||||
[] as SuperBlocks[]
|
||||
);
|
||||
}, [] as SuperBlocks[]);
|
||||
})();
|
||||
|
||||
export function buildExtCurriculumDataV2(
|
||||
curriculum: Curriculum<CurriculumProps>
|
||||
@ -330,7 +346,7 @@ export function buildExtCurriculumDataV2(
|
||||
);
|
||||
|
||||
writeToFile('available-superblocks', {
|
||||
superblocks: orderedSuperBlockInfo
|
||||
superblocks: orderedSuperBlockInfo()
|
||||
});
|
||||
|
||||
for (const superBlockKey of superBlockKeys) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user