mirror of
https://github.com/outline/outline.git
synced 2026-05-06 18:12:33 -05:00
flow
This commit is contained in:
+1
-2
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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> doesn’t contain any documents yet.": "<0>{{ collectionName }}</0> enthält noch keine Dokumente.",
|
||||
"Get started by creating a new one!": "Erstellen Sie jetzt ein neues!"
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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> doesn’t 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!"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user