added translations

This commit is contained in:
Fedor Katurov 2023-04-26 22:00:16 +06:00
parent 00a11d05e0
commit 378687df52
13 changed files with 156 additions and 15 deletions

View file

@ -27,8 +27,12 @@
"classnames": "^2.3.2", "classnames": "^2.3.2",
"dockview": "^1.7.1", "dockview": "^1.7.1",
"formik": "^2.2.9", "formik": "^2.2.9",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.2.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^12.2.2",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"remirror": "^2.0.26", "remirror": "^2.0.26",
"sass": "^1.62.0", "sass": "^1.62.0",

View file

@ -0,0 +1,10 @@
{
"Edit": "Edit",
"Save": "Save",
"Delete": "Delete",
"Nothing's here yet": "Nothing's here yet",
"Background": "Background",
"Text": "Text",
"Links": "Links",
"Colors": "Colors"
}

View file

@ -0,0 +1,10 @@
{
"Edit": "Изменить",
"Save": "Сохранить",
"Delete": "Удалить",
"Nothing's here yet": "Здесь ничего нет",
"Background": "Фон",
"Text": "Текст",
"Links": "Ссылки",
"Colors": "Цвета"
}

16
src/i18n/index.ts Normal file
View file

@ -0,0 +1,16 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.use(Backend)
.use(LanguageDetector)
.init({
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false,
},
});

11
src/i18next.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
import "i18next";
import en from "../public/locales/en/translation.json";
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "en";
resources: {
en: typeof en;
};
}
}

View file

@ -2,10 +2,12 @@ import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { Editor } from "~/pages/editor"; import { Editor } from "~/pages/editor";
import "./styles/main.scss";
import { ThemeProvider } from "./modules/theme/containers/ThemeProvider"; import { ThemeProvider } from "./modules/theme/containers/ThemeProvider";
import { SettingsProvider } from "./modules/settings/providers/SettingsProvider"; import { SettingsProvider } from "./modules/settings/providers/SettingsProvider";
import "./i18n";
import "./styles/main.scss";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode> <React.StrictMode>
<SettingsProvider> <SettingsProvider>

View file

@ -1,6 +1,7 @@
import { FC, PropsWithChildren } from "react"; import { FC, PropsWithChildren } from "react";
import styles from "./styles.module.scss"; import { useTranslation } from "react-i18next";
import { Button } from "~/components/buttons/Button"; import { Button } from "~/components/buttons/Button";
import styles from "./styles.module.scss";
interface EditorWrapperProps extends PropsWithChildren { interface EditorWrapperProps extends PropsWithChildren {
save: () => void; save: () => void;
@ -8,19 +9,21 @@ interface EditorWrapperProps extends PropsWithChildren {
} }
const EditorWrapper: FC<EditorWrapperProps> = ({ children, save, remove }) => { const EditorWrapper: FC<EditorWrapperProps> = ({ children, save, remove }) => {
const { t } = useTranslation();
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<div className={styles.content}>{children}</div> <div className={styles.content}>{children}</div>
<div className={styles.panel}> <div className={styles.panel}>
<Button onClick={remove} role="button" size="small" variant="outline"> <Button onClick={remove} role="button" size="small" variant="outline">
Delete {t("Delete")}
</Button> </Button>
<div className={styles.filler} /> <div className={styles.filler} />
<Button onClick={save} role="button" size="small"> <Button onClick={save} role="button" size="small">
Save {t("Save")}
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -1,7 +1,8 @@
import { FC } from "react"; import { FC } from "react";
import styles from "./styles.module.scss"; import { useTranslation } from "react-i18next";
import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings";
import { Button } from "~/components/buttons/Button"; import { Button } from "~/components/buttons/Button";
import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings";
import styles from "./styles.module.scss";
interface EmptyViewerProps { interface EmptyViewerProps {
startEditing?: () => void; startEditing?: () => void;
@ -9,10 +10,11 @@ interface EmptyViewerProps {
const EmptyViewer: FC<EmptyViewerProps> = ({ startEditing }) => { const EmptyViewer: FC<EmptyViewerProps> = ({ startEditing }) => {
const style = useContainerPaddings(); const style = useContainerPaddings();
const { t } = useTranslation();
return ( return (
<div className={styles.empty} style={style}> <div className={styles.empty} style={style}>
<div className={styles.title}>Nothing's here</div> <div className={styles.title}>{t(`Nothing's here yet`)}</div>
<div> <div>
<Button <Button
onClick={startEditing} onClick={startEditing}
@ -20,7 +22,7 @@ const EmptyViewer: FC<EmptyViewerProps> = ({ startEditing }) => {
variant="outline" variant="outline"
size="small" size="small"
> >
Edit it {t("Edit")}
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown";
import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings"; import { useContainerPaddings } from "~/modules/theme/hooks/useContainerPaddings";
import styles from "./styles.module.scss"; import styles from "./styles.module.scss";
import { Button } from "~/components/buttons/Button"; import { Button } from "~/components/buttons/Button";
import { useTranslation } from "react-i18next";
interface ReactMarkdownViewerProps { interface ReactMarkdownViewerProps {
value: string; value: string;
@ -13,6 +14,7 @@ const ReactMarkdownViewer: FC<ReactMarkdownViewerProps> = ({
value, value,
startEditing, startEditing,
}) => { }) => {
const { t } = useTranslation();
const style = useContainerPaddings(); const style = useContainerPaddings();
return ( return (
@ -24,7 +26,7 @@ const ReactMarkdownViewer: FC<ReactMarkdownViewerProps> = ({
role="button" role="button"
onClick={startEditing} onClick={startEditing}
> >
Edit {t("Edit")}
</Button> </Button>
</div> </div>

View file

@ -1,8 +1,10 @@
import { ChangeEvent, FC, useCallback } from "react"; import { ChangeEvent, FC, useCallback } from "react";
import { useSettings } from "../../context/SettingsContext"; import { useSettings } from "../../context/SettingsContext";
import { useTranslation } from "react-i18next";
const SettingsContainer: FC = () => { const SettingsContainer: FC = () => {
const { update, settings } = useSettings(); const { update, settings } = useSettings();
const { t } = useTranslation();
const updateBackgroundColor = useCallback( const updateBackgroundColor = useCallback(
(event: ChangeEvent<HTMLInputElement>) => { (event: ChangeEvent<HTMLInputElement>) => {
@ -26,8 +28,10 @@ const SettingsContainer: FC = () => {
return ( return (
<div> <div>
<h2>{t("Colors")}</h2>
<label htmlFor="color"> <label htmlFor="color">
Background {t("Background")}
<input <input
type="color" type="color"
id="color" id="color"
@ -37,7 +41,8 @@ const SettingsContainer: FC = () => {
</label> </label>
<label htmlFor="color"> <label htmlFor="color">
Text {t("Text")}
<input <input
type="color" type="color"
id="color" id="color"
@ -47,7 +52,8 @@ const SettingsContainer: FC = () => {
</label> </label>
<label htmlFor="color"> <label htmlFor="color">
Link {t("Links")}
<input <input
type="color" type="color"
id="color" id="color"

View file

@ -21,4 +21,5 @@ export const SettingsContext = createContext({
show: () => {}, show: () => {},
hide: () => {}, hide: () => {},
}); });
export const useSettings = () => useContext(SettingsContext); export const useSettings = () => useContext(SettingsContext);

View file

@ -23,8 +23,9 @@
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"~/*": ["./src/*"] "~/*": ["./src/*"]
}
}, },
"include": ["src"], "types": ["./src/vite-env.d.ts", "./src/i18next.d.ts"]
},
"include": ["src", "public/locales"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }

View file

@ -157,7 +157,7 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
"@babel/runtime@7.21.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": "@babel/runtime@7.21.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.21.0" version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@ -3028,6 +3028,13 @@ create-context-state@^2.0.0:
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
cross-fetch@3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
dependencies:
node-fetch "2.6.7"
cross-spawn@^7.0.0, cross-spawn@^7.0.2: cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -4255,6 +4262,13 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1:
dependencies: dependencies:
react-is "^16.7.0" react-is "^16.7.0"
html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"
htmlparser2@^8.0.1: htmlparser2@^8.0.1:
version "8.0.2" version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
@ -4297,6 +4311,27 @@ hyphenate-style-name@^1.0.3:
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
i18next-browser-languagedetector@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.1.tgz#ead34592edc96c6c3a618a51cb57ad027c5b5d87"
integrity sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==
dependencies:
"@babel/runtime" "^7.19.4"
i18next-http-backend@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.2.0.tgz#f77c06dc8e766c7588bb38da2f5fa0614ba67b3f"
integrity sha512-Z4sM7R6tzdLknSPER9GisEBxKPg5FkI07UrQniuroZmS15PHQrcCPLyuGKj8SS68tf+O2aEDYSUnmy1TZqZSbw==
dependencies:
cross-fetch "3.1.5"
i18next@^22.4.15:
version "22.4.15"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.15.tgz#951882b751872994f8502b5a6ef6f796e6a7d7f8"
integrity sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==
dependencies:
"@babel/runtime" "^7.20.6"
idb-keyval@^5.0.2: idb-keyval@^5.0.2:
version "5.1.5" version "5.1.5"
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.5.tgz#be11174bac0cb756dba4cc86fb36b6cd63f5ce6d" resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.5.tgz#be11174bac0cb756dba4cc86fb36b6cd63f5ce6d"
@ -5530,6 +5565,13 @@ node-domexception@^1.0.0:
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-fetch@3.3.1: node-fetch@3.3.1:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.1.tgz#b3eea7b54b3a48020e46f4f88b9c5a7430d20b2e" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.1.tgz#b3eea7b54b3a48020e46f4f88b9c5a7430d20b2e"
@ -6222,6 +6264,14 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-i18next@^12.2.2:
version "12.2.2"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.2.2.tgz#38a6fad11acf4f2abfc5611bdb6b1918d0f47578"
integrity sha512-KBB6buBmVKXUWNxXHdnthp+38gPyBT46hJCAIQ8rX19NFL/m2ahte2KARfIDf2tMnSAL7wwck6eDOd/9zn6aFg==
dependencies:
"@babel/runtime" "^7.20.6"
html-parse-stringify "^3.0.1"
react-is@^16.13.1, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -7197,6 +7247,11 @@ tough-cookie@~2.5.0:
psl "^1.1.28" psl "^1.1.28"
punycode "^2.1.1" punycode "^2.1.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
trim-lines@^3.0.0: trim-lines@^3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
@ -7517,6 +7572,11 @@ vite@^4.3.0:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
w3c-keyname@^2.2.0, w3c-keyname@^2.2.4: w3c-keyname@^2.2.0, w3c-keyname@^2.2.4:
version "2.2.6" version "2.2.6"
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.6.tgz#8412046116bc16c5d73d4e612053ea10a189c85f" resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.6.tgz#8412046116bc16c5d73d4e612053ea10a189c85f"
@ -7580,6 +7640,19 @@ web-streams-polyfill@^3.0.3:
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
when@3.7.7: when@3.7.7:
version "3.7.7" version "3.7.7"
resolved "https://registry.yarnpkg.com/when/-/when-3.7.7.tgz#aba03fc3bb736d6c88b091d013d8a8e590d84718" resolved "https://registry.yarnpkg.com/when/-/when-3.7.7.tgz#aba03fc3bb736d6c88b091d013d8a8e590d84718"