mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
0139b91b5d
* chore: Replace lodash with es-toolkit Migrate all direct lodash imports to es-toolkit/compat for a smaller, faster, lodash-compatible utility library. Transitive lodash usage from other packages remains unchanged. * fix: Restore isPlainObject semantics in CanCan policy The lodash migration aliased `isObject` to `lodash/isPlainObject` and the codemod incorrectly mapped the local name to es-toolkit's `isObject`, which also returns true for arrays and functions. This caused condition objects in policy definitions to be skipped, breaking authorization checks across the codebase. * fix: Restore unicode-aware length counting in validators es-toolkit/compat's size() returns string.length, while lodash's _.size() counts unicode code points. Switch to [...value].length to preserve the previous behavior so multi-byte characters like emoji count as one.
93 lines
2.3 KiB
TypeScript
93 lines
2.3 KiB
TypeScript
import { m } from "framer-motion";
|
||
import { find } from "es-toolkit/compat";
|
||
import { useTranslation } from "react-i18next";
|
||
import styled from "styled-components";
|
||
import { languages, languageOptions } from "@shared/i18n";
|
||
import ButtonLink from "~/components/ButtonLink";
|
||
import Flex from "~/components/Flex";
|
||
import env from "~/env";
|
||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||
import useStores from "~/hooks/useStores";
|
||
import { detectLanguage } from "~/utils/language";
|
||
import { LanguageIcon } from "./Icons/LanguageIcon";
|
||
import Text from "./Text";
|
||
|
||
export default function LanguagePrompt() {
|
||
const { ui } = useStores();
|
||
const { t } = useTranslation();
|
||
const user = useCurrentUser();
|
||
const language = detectLanguage();
|
||
|
||
if (
|
||
language === "en_US" ||
|
||
language === user.language ||
|
||
!languages.includes(language)
|
||
) {
|
||
return null;
|
||
}
|
||
|
||
const option = find(languageOptions, (o) => o.value === language);
|
||
const optionLabel = option ? option.label : "";
|
||
const appName = env.APP_NAME;
|
||
|
||
return (
|
||
<Wrapper
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -20 }}
|
||
transition={{ duration: 0.2 }}
|
||
>
|
||
<Flex align="center" gap={12}>
|
||
<LanguageIcon />
|
||
<span>
|
||
<Text>
|
||
{appName} is available in your language – {optionLabel}, would you
|
||
like to change?
|
||
</Text>
|
||
<br />
|
||
<Link
|
||
onClick={async () => {
|
||
ui.set({ languagePromptDismissed: true });
|
||
await user.save({ language });
|
||
}}
|
||
>
|
||
{t("Change Language")}
|
||
</Link>{" "}
|
||
·{" "}
|
||
<Link onClick={() => ui.set({ languagePromptDismissed: true })}>
|
||
{t("Dismiss")}
|
||
</Link>
|
||
</span>
|
||
</Flex>
|
||
</Wrapper>
|
||
);
|
||
}
|
||
|
||
const Wrapper = styled(m.p)`
|
||
color: ${(props) => props.theme.text};
|
||
border: 1px solid ${(props) => props.theme.divider};
|
||
padding: 20px;
|
||
margin-top: 12px;
|
||
border-radius: 8px;
|
||
position: relative;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||
|
||
a {
|
||
color: ${(props) => props.theme.text};
|
||
font-weight: 500;
|
||
}
|
||
|
||
a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
`;
|
||
|
||
const Link = styled(ButtonLink)`
|
||
cursor: var(--cursor-pointer);
|
||
color: ${(props) => props.theme.text};
|
||
font-weight: 500;
|
||
|
||
&:hover {
|
||
text-decoration: underline;
|
||
}
|
||
`;
|