diff --git a/src/components/status/assessment.js b/src/components/status/assessment.js index a6a9d366f..430fcc9a5 100644 --- a/src/components/status/assessment.js +++ b/src/components/status/assessment.js @@ -8,12 +8,14 @@ import useOption from "lib/hooks/use-option"; import useTranslate from "lib/hooks/use-translate"; import {activeState} from "lib/recoil"; import style from "./style.scss"; +import useStatusTranslate from "./use-status-translate"; function Button({assessment}) { const listener = useListener(); const options = useOption("status") || {}; const redirect = get(options, "allowRedirect", true); const setActive = useSetRecoilState(activeState); + const statusTranslate = useStatusTranslate({assessment}); const translate = useTranslate(); if(assessment.completed) { @@ -25,7 +27,7 @@ function Button({assessment}) { } if(assessment.link && redirect) { - return {translate("status.start")}; + return {statusTranslate("start")}; } if(assessment.loading) { @@ -37,7 +39,7 @@ function Button({assessment}) { setActive({...assessment}); }; - return ; + return ; } Button.propTypes = { @@ -50,8 +52,11 @@ Button.propTypes = { }; function Assessment({assessment}) { + const statusTranslate = useStatusTranslate({assessment}); const translate = useTranslate(); - const surveyName = assessment.surveyName || translate(`survey.${assessment.surveyType}_assessment`); + const surveyName = statusTranslate("survey") + || assessment.surveyName + || translate(`survey.${assessment.surveyType}_assessment`); return (
diff --git a/src/components/status/error.js b/src/components/status/error.js index 57de0e6b8..53221a1bb 100644 --- a/src/components/status/error.js +++ b/src/components/status/error.js @@ -3,26 +3,26 @@ import {useRecoilRefresher_UNSTABLE as useRecoilRefresher} from "recoil"; import HelpButton from "components/common/help/button"; import HelpModal from "components/common/help/modal"; import useOption from "lib/hooks/use-option"; -import useTranslate from "lib/hooks/use-translate"; import {orderState} from "lib/recoil"; import style from "./style.scss"; +import useStatusTranslate from "./use-status-translate"; export default function Error() { const refreshOrder = useRecoilRefresher(orderState); const showHelp = useOption("showHelp"); const [showHelpModal, setShowHelpModal] = useState(false); - const translate = useTranslate(); + const translate = useStatusTranslate(); const retry = () => { refreshOrder(); }; return (
{showHelp && } -
{translate("status.heading")}
+
{translate("heading")}
{showHelp && setShowHelpModal(true)} />}
-
{translate("status.error")}
- +
{translate("error")}
+ {showHelpModal && }
); diff --git a/src/components/status/index.js b/src/components/status/index.js index ac2d393fc..586628f9c 100644 --- a/src/components/status/index.js +++ b/src/components/status/index.js @@ -1,17 +1,18 @@ import {useEffect, useState} from "react"; import HelpButton from "components/common/help/button"; import HelpModal from "components/common/help/modal"; +import Markdown from "components/common/markdown"; import useActive from "lib/hooks/use-active"; import useComponentEvents from "lib/hooks/use-component-events"; import useOption from "lib/hooks/use-option"; import useOrder from "lib/hooks/use-order"; -import useTranslate from "lib/hooks/use-translate"; import Assessment from "./assessment"; import Error from "./error"; import Loading from "./loading"; import Skipped from "./skipped"; import style from "./style.scss"; import Timeout from "./timeout"; +import useStatusTranslate from "./use-status-translate"; export default function Status() { const active = useActive(); @@ -19,7 +20,7 @@ export default function Status() { const order = useOrder(); const showHelp = useOption("showHelp"); const [showHelpModal, setShowHelpModal] = useState(false); - const translate = useTranslate(); + const translate = useStatusTranslate(); const assessments = order?.assessments || []; useComponentEvents("Status", {order}); @@ -44,10 +45,10 @@ export default function Status() {
{showHelp && } -
{translate("status.heading")}
+
{translate("heading")}
{showHelp && setShowHelpModal(true)} />}
-
{translate("status.text")}
+ {translate("text")} {assessments.map((assessment) => )} {showHelpModal && }
diff --git a/src/components/status/loading.js b/src/components/status/loading.js index 3a4a5b6ec..297f31421 100644 --- a/src/components/status/loading.js +++ b/src/components/status/loading.js @@ -1,13 +1,13 @@ -import useTranslate from "lib/hooks/use-translate"; import style from "./style.scss"; +import useStatusTranslate from "./use-status-translate"; export default function Loading() { - const translate = useTranslate(); + const translate = useStatusTranslate(); return (
-
{translate("status.loading")}
+
{translate("loading")}
); } diff --git a/src/components/status/style.scss b/src/components/status/style.scss index 008db27c5..80f433b05 100644 --- a/src/components/status/style.scss +++ b/src/components/status/style.scss @@ -28,6 +28,10 @@ max-width: $breakpoint-sm; padding: $buffer-lg; + :global(.traitify--markdown) { + margin-bottom: $buffer-section; + text-align: center; + } .retry { display: block; margin: 0 auto; diff --git a/src/components/status/timeout.js b/src/components/status/timeout.js index 99585fa9b..76c4c295e 100644 --- a/src/components/status/timeout.js +++ b/src/components/status/timeout.js @@ -1,17 +1,17 @@ import useListener from "lib/hooks/use-listener"; -import useTranslate from "lib/hooks/use-translate"; import style from "./style.scss"; +import useStatusTranslate from "./use-status-translate"; export default function Timeout() { const listener = useListener(); - const translate = useTranslate(); + const translate = useStatusTranslate(); const retry = () => { listener.trigger("Order.polling", {status: "on"}); }; return (
-
{translate("status.timeout")}
- +
{translate("timeout")}
+
); } diff --git a/src/components/status/use-status-translate.js b/src/components/status/use-status-translate.js new file mode 100644 index 000000000..928a5c6d6 --- /dev/null +++ b/src/components/status/use-status-translate.js @@ -0,0 +1,23 @@ +import {useCallback} from "react"; +import useOrder from "lib/hooks/use-order"; +import useTranslate from "lib/hooks/use-translate"; + +export default function useStatusTranslate({assessment} = {}) { + const order = useOrder(); + const translate = useTranslate(); + + return useCallback((key, ...options) => { + const assessments = assessment ? [assessment] : order?.assessments || []; + const cognitive = assessments.some(({surveyType}) => surveyType === "cognitive"); + const personality = assessments.some(({surveyType}) => surveyType === "personality"); + const interview = assessments.some(({vendor}) => vendor === "crosschq"); + const keys = [ + personality && interview && "status.personality_interview", + cognitive && interview && "status.cognitive_interview", + interview && "status.interview", + "status" + ].filter(Boolean).map((prefix) => [prefix, key].join(".")); + + return translate(keys, ...options); + }, [assessment, order, translate]); +} diff --git a/src/lib/common/array/find-map.js b/src/lib/common/array/find-map.js new file mode 100644 index 000000000..eba737b3e --- /dev/null +++ b/src/lib/common/array/find-map.js @@ -0,0 +1,8 @@ +export default function findMap(array, callback) { + let result; + array.some((item, index) => { + result = callback(item, index); + return result; + }); + return result || undefined; +} diff --git a/src/lib/common/order-from-query.js b/src/lib/common/order-from-query.js index e38650254..b973069c8 100644 --- a/src/lib/common/order-from-query.js +++ b/src/lib/common/order-from-query.js @@ -11,7 +11,8 @@ export function assessmentFromQuery(response) { profileID: response.profile_id || response.profileId || dig(response, "profile_ids", 0), skipped: response.isSkipped || response.skipped || false, surveyID: response.deck_id || response.surveyId || response.surveyKey, - surveyName: response.surveyName || response.name + surveyName: response.surveyName || response.name, + vendor: response.vendor }; // NOTE: Prevent overriding with blanks diff --git a/src/lib/i18n-data/en-us.json b/src/lib/i18n-data/en-us.json index ced98b4c3..164d046d6 100644 --- a/src/lib/i18n-data/en-us.json +++ b/src/lib/i18n-data/en-us.json @@ -213,8 +213,20 @@ "slide_error": "Unable to load more slides at this time", "sort": "Sort", "status": { + "cognitive_interview": { + "heading": "Cognitive Assessment & Interview" + }, "error": "There was an error with your assessments", "heading": "Your Application Assessments", + "interview": { + "heading": "Interview", + "start": "Start Interview", + "survey": "Crosschq AI Interviewer", + "text": "Dive into an AI video chat to ask questions, practice your interview, and learn more about the details of this job.\n\nThis is a chat with an Artificial Employee that has been trained on our company culture and the details around this job." + }, + "interview_personality": { + "heading": "Personality Assessment & Interview" + }, "loading": "Loading your assessment", "start": "Start Assessment", "text": "As part of your application, we'd like to ask you to complete the following assessments. Please click on the button next to the assessment name. This will take you to where you'll complete the assessment(s). Please return here once you've completed all assessments.", @@ -245,4 +257,4 @@ "try_again": "Click Here to Try Again", "view_on_onet": "View Career Details on O*Net", "yes": "Yes" -} \ No newline at end of file +} diff --git a/src/lib/i18n.js b/src/lib/i18n.js index 03958991a..686b086ab 100644 --- a/src/lib/i18n.js +++ b/src/lib/i18n.js @@ -1,5 +1,7 @@ +import findMap from "./common/array/find-map"; import dig from "./common/object/dig"; import merge from "./common/object/merge"; +import {isArray} from "./common/object/type"; import i18nData from "./i18n-data"; export default class I18n { @@ -31,10 +33,14 @@ export default class I18n { ...originData }; }; + // NOTE: Returns first translation if key is an array translate = (locale, _key, options) => { - const keys = _key.split("."); - let result = dig(this.data, locale.toLowerCase(), ...keys); - if(!result && locale.toLowerCase() !== "en-us") { result = dig(this.data, "en-us", ...keys); } + const keys = isArray(_key) ? _key : [_key]; + let result = findMap(keys, (key) => dig(this.data, locale.toLowerCase(), ...key.split("."))); + + if(!result && locale.toLowerCase() !== "en-us") { + result = findMap(keys, (key) => dig(this.data, "en-us", ...key.split("."))); + } if(!result || !options) { return result; } return result.replace(/%\{[a-z_]*\}/g, (r) => options[r.slice(2, -1)]); diff --git a/test/components/__snapshots__/status.test.js.snap b/test/components/__snapshots__/status.test.js.snap index 750d62108..c2e0d1fe2 100644 --- a/test/components/__snapshots__/status.test.js.snap +++ b/test/components/__snapshots__/status.test.js.snap @@ -52,11 +52,11 @@ exports[`Status order renders component 1`] = ` />
-
As part of your application, we'd like to ask you to complete the following assessments. Please click on the button next to the assessment name. This will take you to where you'll complete the assessment(s). Please return here once you've completed all assessments. -
+

@@ -280,11 +280,11 @@ exports[`Status recommendation renders component 1`] = ` />
-
As part of your application, we'd like to ask you to complete the following assessments. Please click on the button next to the assessment name. This will take you to where you'll complete the assessment(s). Please return here once you've completed all assessments. -
+

diff --git a/test/components/status.test.js b/test/components/status.test.js index f9aa28ee0..811c930a6 100644 --- a/test/components/status.test.js +++ b/test/components/status.test.js @@ -25,6 +25,7 @@ import recommendationCompleted from "support/data/recommendation/completed"; import recommendationIncomplete from "support/data/recommendation/incomplete"; import flushAsync from "support/flush-async"; import useContainer from "support/hooks/use-container"; +import useGlobalMock from "support/hooks/use-global-mock"; describe("Status", () => { let component; @@ -56,6 +57,8 @@ describe("Status", () => { describe("order", () => { let orderResponse; + useGlobalMock(console, "warn"); + beforeEach(() => { orderResponse = mutable(orderCompleted); order = orderFromQuery({data: {order: orderResponse}}); diff --git a/test/lib/common/array/find-map.test.js b/test/lib/common/array/find-map.test.js new file mode 100644 index 000000000..fb1240a81 --- /dev/null +++ b/test/lib/common/array/find-map.test.js @@ -0,0 +1,38 @@ +import findMap from "lib/common/array/find-map"; + +describe("Array", () => { + describe("findMap", () => { + it("returns first mapped truthy value", () => { + const value = findMap([1, 2, 3], (item) => item > 1 && item * 10); + + expect(value).toBe(20); + }); + + it("returns undefined when nothing matches", () => { + const value = findMap([1, 2, 3], () => null); + + expect(value).toBeUndefined(); + }); + + it("returns undefined for empty array", () => { + const value = findMap([], () => "anything"); + + expect(value).toBeUndefined(); + }); + + it("passes item and index to callback", () => { + const callback = jest.fn(); + findMap(["a", "b"], callback); + + expect(callback).toHaveBeenCalledWith("a", 0); + expect(callback).toHaveBeenCalledWith("b", 1); + }); + + it("short-circuits after first truthy result", () => { + const callback = jest.fn((item) => item === "b" && item.toUpperCase()); + findMap(["a", "b", "c"], callback); + + expect(callback).toHaveBeenCalledTimes(2); + }); + }); +}); diff --git a/test/lib/i18n.test.js b/test/lib/i18n.test.js index fe653b87c..e431d9818 100644 --- a/test/lib/i18n.test.js +++ b/test/lib/i18n.test.js @@ -109,7 +109,24 @@ describe("I18n", () => { it("allows misses", () => { const translation = translate("tacos"); - expect(translation).toBeNull(); + expect(translation).toBeUndefined(); + }); + + describe("with array of keys", () => { + it("returns first match", () => { + i18n.addTranslations("en-US", {data: {tacos: "I like tacos", pizza: "I like pizza"}}); + const translation = translate(["burgers", "tacos", "pizza"]); + + expect(translation).toBe("I like tacos"); + }); + + it("prefers current locale", () => { + i18n.addTranslations("en-US", {data: {tacos: "I like tacos"}}); + i18n.addTranslations("es-US", {data: {pizza: "Me gusta pizza"}}); + const translation = translateES(["tacos", "pizza"]); + + expect(translation).toBe("Me gusta pizza"); + }); }); }); });