This commit is contained in:
Tom Moor
2020-11-14 11:33:07 -08:00
parent bef9176add
commit 08b237343b
10 changed files with 142 additions and 30 deletions
+1 -2
View File
@@ -61,5 +61,4 @@ SMTP_REPLY_EMAIL=
# Custom logo that displays on the authentication screen, scaled to height: 60px
# TEAM_LOGO=https://example.com/images/logo.png
DEFAULT_LANGUAGE=en_US
AVAILABLE_LANGUAGES=["en_US", "de_DE", "pt_PT"]
DEFAULT_LANGUAGE=en_US
+15 -3
View File
@@ -20,11 +20,16 @@ const Select = styled.select`
}
`;
const Wrapper = styled.label`
max-width: ${(props) => (props.short ? "350px" : "100%")};
`;
type Option = { label: string, value: string };
export type Props = {
value?: string,
label?: string,
short?: boolean,
className?: string,
labelHidden?: boolean,
options: Option[],
@@ -43,12 +48,19 @@ class InputSelect extends React.Component<Props> {
};
render() {
const { label, className, labelHidden, options, ...rest } = this.props;
const {
label,
className,
labelHidden,
options,
short,
...rest
} = this.props;
const wrappedLabel = <LabelText>{label}</LabelText>;
return (
<label>
<Wrapper short={short}>
{label &&
(labelHidden ? (
<VisuallyHidden>{wrappedLabel}</VisuallyHidden>
@@ -64,7 +76,7 @@ class InputSelect extends React.Component<Props> {
))}
</Select>
</Outline>
</label>
</Wrapper>
);
}
}
+6 -1
View File
@@ -11,14 +11,17 @@ import UserDelete from "scenes/UserDelete";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Flex from "components/Flex";
import HelpText from "components/HelpText";
import Input, { LabelText } from "components/Input";
import InputSelect from "components/InputSelect";
import PageTitle from "components/PageTitle";
import ImageUpload from "./components/ImageUpload";
import { type Translate } from "types";
type Props = {
auth: AuthStore,
ui: UiStore,
t: Translate,
};
@withTranslation()
@@ -120,7 +123,6 @@ class Profile extends React.Component<Props> {
required
short
/>
<label>*Experimental/Beta feature</label>
<br />
<InputSelect
label={t("Language")}
@@ -133,6 +135,9 @@ class Profile extends React.Component<Props> {
onChange={this.handleLanguageChange}
short
/>
<HelpText small>
{t("Please note that translations are currently in early access.")}
</HelpText>
<Button type="submit" disabled={isSaving || !this.isValid}>
{isSaving ? t("Saving…") : t("Save")}
</Button>
+93
View File
@@ -0,0 +1,93 @@
// @flow
declare module "react-i18next" {
declare type TFunction = (key?: ?string, data?: ?Object) => string;
declare type TranslatorProps = {|
t: TFunction,
i18nLoadedAt: Date,
i18n: Object,
|};
declare type TranslatorPropsVoid = {
t: TFunction | void,
i18nLoadedAt: Date | void,
i18n: Object | void,
};
declare type Translator<P: {}, Component: React$ComponentType<P>> = (
WrappedComponent: Component
) => React$ComponentType<
$Diff<React$ElementConfig<Component>, TranslatorPropsVoid>
>;
declare type TranslateOptions = $Shape<{
wait: boolean,
nsMode: "default" | "fallback",
bindi18n: false | string,
bindStore: false | string,
withRef: boolean,
translateFuncName: string,
i18n: Object,
usePureComponent: boolean,
}>;
declare function withTranslation<P: {}, Component: React$ComponentType<P>>(
namespaces?: | string
| Array<string>
| (($Diff<P, TranslatorPropsVoid>) => string | Array<string>),
options?: TranslateOptions
): Translator<P, Component>;
declare type I18nProps = {
i18n?: Object,
ns?: string | Array<string>,
children: (t: TFunction, { i18n: Object, t: TFunction }) => React$Node,
initialI18nStore?: Object,
initialLanguage?: string,
};
declare var I18n: React$ComponentType<I18nProps>;
declare type InterpolateProps = {
className?: string,
dangerouslySetInnerHTMLPartElement?: string,
i18n?: Object,
i18nKey?: string,
options?: Object,
parent?: string,
style?: Object,
t?: TFunction,
useDangerouslySetInnerHTML?: boolean,
};
declare var Interpolate: React$ComponentType<InterpolateProps>;
declare type TransProps = {
count?: number,
parent?: string,
i18n?: Object,
i18nKey?: string,
t?: TFunction,
};
declare var Trans: React$ComponentType<TransProps>;
declare type ProviderProps = { i18n: Object, children: React$Element<*> };
declare var I18nextProvider: React$ComponentType<ProviderProps>;
declare type NamespacesProps = {
components: Array<React$ComponentType<*>>,
i18n: { loadNamespaces: Function },
};
declare function loadNamespaces(props: NamespacesProps): Promise<void>;
declare var initReactI18next: {
type: "3rdParty",
init: (instance: Object) => void,
};
declare function setDefaults(options: TranslateOptions): void;
declare function getDefaults(): TranslateOptions;
declare function getI18n(): Object;
declare function setI18n(instance: Object): void;
}
+1 -7
View File
@@ -66,13 +66,7 @@ router.post("users.update", auth(), async (ctx) => {
if (name) user.name = name;
if (avatarUrl) user.avatarUrl = avatarUrl;
if (language) {
if (process.env.AVAILABLE_LANGUAGES.includes(language)) {
user.language = language;
} else {
user.language = process.env.DEFAULT_LANGUAGE;
}
}
if (language) user.language = language;
await user.save();
+4
View File
@@ -4,6 +4,7 @@ import addMinutes from "date-fns/add_minutes";
import subMinutes from "date-fns/sub_minutes";
import JWT from "jsonwebtoken";
import uuid from "uuid";
import { languages } from "../../shared/translations/i18n";
import { ValidationError } from "../errors";
import { sendEmail } from "../mailer";
import { DataTypes, sequelize, encryptedFields } from "../sequelize";
@@ -39,6 +40,9 @@ const User = sequelize.define(
language: {
type: DataTypes.STRING,
defaultValue: process.env.DEFAULT_LANGUAGE,
validate: {
isIn: [languages],
},
},
},
{
+2 -2
View File
@@ -1,5 +1,5 @@
{
"(You)": "(Sie)",
"You": "Sie",
"currently editing": "im Moment am bearbeiten",
"currently viewing": "im Moment am anschauen",
"viewed {{ timeAgo }} ago": "sah vor {{ timeAgo }}",
@@ -143,4 +143,4 @@
"Delete account": "Konto löschen",
"<0>{{collectionName}}</0> doesnt contain any documents yet.": "<0>{{ collectionName }}</0> enthält noch keine Dokumente.",
"Get started by creating a new one!": "Erstellen Sie jetzt ein neues!"
}
}
+2 -1
View File
@@ -1,8 +1,8 @@
{
"(You)": "(You)",
"currently editing": "currently editing",
"currently viewing": "currently viewing",
"viewed {{ timeAgo }} ago": "viewed {{ timeAgo }} ago",
"You": "You",
"Trash": "Trash",
"Archive": "Archive",
"Drafts": "Drafts",
@@ -138,6 +138,7 @@
"Upload": "Upload",
"Full name": "Full name",
"Language": "Language",
"Please note that translations are currently in early access.": "Please note that translations are currently in early access.",
"Saving…": "Saving…",
"Save": "Save",
"Delete Account": "Delete Account",
+16 -12
View File
@@ -6,6 +6,18 @@ import de_DE from "./de_DE.json";
import en_US from "./default.json";
import pt_PT from "./pt_PT.json";
const resources = {
en_US: {
translation: en_US,
},
de_DE: {
translation: de_DE,
},
pt_PT: {
translation: pt_PT,
},
};
const initI18n = () => {
i18n.use(initReactI18next).init({
interpolation: {
@@ -20,18 +32,10 @@ const initI18n = () => {
: "en_US",
debug: process.env.NODE_ENV !== "production",
keySeparator: false,
resources: {
en_US: {
translation: en_US,
},
de_DE: {
translation: de_DE,
},
pt_PT: {
translation: pt_PT,
},
},
resources,
});
};
export { initI18n, i18n, en_US, de_DE, pt_PT };
const languages: string[] = Object.keys(resources);
export { initI18n, languages, i18n, en_US, de_DE, pt_PT };
+2 -2
View File
@@ -1,5 +1,5 @@
{
"(You)": "(Tu)",
"You": "Tu",
"currently editing": "neste momento a editar",
"currently viewing": "neste momento a visualizar",
"viewed {{ timeAgo }} ago": "viu há {{ timeAgo }}",
@@ -143,4 +143,4 @@
"Delete account": "Apagar conta",
"<0>{{collectionName}}</0> doesnt contain any documents yet.": "<0>{{ collectionName }}</0> não contém nenhum documento ainda.",
"Get started by creating a new one!": "Para começar, crie um documento novo!"
}
}