diff --git a/app/components/LocaleTime.js b/app/components/LocaleTime.js
new file mode 100644
index 000000000..f27c8fc97
--- /dev/null
+++ b/app/components/LocaleTime.js
@@ -0,0 +1,81 @@
+// @flow
+import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
+import format from "date-fns/format";
+import * as React from "react";
+import Tooltip from "components/Tooltip";
+import useUserLocale from "hooks/useUserLocale";
+
+const locales = {
+ en: require(`date-fns/locale/en`),
+ de: require(`date-fns/locale/de`),
+ es: require(`date-fns/locale/es`),
+ fr: require(`date-fns/locale/fr`),
+ ko: require(`date-fns/locale/ko`),
+ pt: require(`date-fns/locale/pt`),
+};
+
+let callbacks = [];
+
+// This is a shared timer that fires every minute, used for
+// updating all Time components across the page all at once.
+setInterval(() => {
+ callbacks.forEach((cb) => cb());
+}, 1000 * 60);
+
+function eachMinute(fn) {
+ callbacks.push(fn);
+
+ return () => {
+ callbacks = callbacks.filter((cb) => cb !== fn);
+ };
+}
+
+type Props = {
+ dateTime: string,
+ children?: React.Node,
+ tooltipDelay?: number,
+ addSuffix?: boolean,
+ shorten?: boolean,
+};
+
+function Time({ addSuffix, children, dateTime, shorten, tooltipDelay }: Props) {
+ const userLocale = useUserLocale();
+ const [_, setMinutesMounted] = React.useState(0); // eslint-disable-line no-unused-vars
+ const callback = React.useRef();
+
+ React.useEffect(() => {
+ callback.current = eachMinute(() => {
+ setMinutesMounted((state) => ++state);
+ });
+
+ return () => {
+ if (callback.current) {
+ callback.current();
+ }
+ };
+ }, []);
+
+ let content = distanceInWordsToNow(dateTime, {
+ addSuffix,
+ locale: locales[userLocale],
+ });
+
+ if (shorten) {
+ content = content
+ .replace("about", "")
+ .replace("less than a minute ago", "just now")
+ .replace("minute", "min");
+ }
+
+ return (
+
+
+
+ );
+}
+
+export default Time;
diff --git a/app/components/Time.js b/app/components/Time.js
index 927984eed..a7e562760 100644
--- a/app/components/Time.js
+++ b/app/components/Time.js
@@ -1,24 +1,8 @@
// @flow
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
-import format from "date-fns/format";
import * as React from "react";
-import Tooltip from "components/Tooltip";
-let callbacks = [];
-
-// This is a shared timer that fires every minute, used for
-// updating all Time components across the page all at once.
-setInterval(() => {
- callbacks.forEach((cb) => cb());
-}, 1000 * 60);
-
-function eachMinute(fn) {
- callbacks.push(fn);
-
- return () => {
- callbacks = callbacks.filter((cb) => cb !== fn);
- };
-}
+const LocaleTime = React.lazy(() => import("components/LocaleTime"));
type Props = {
dateTime: string,
@@ -28,44 +12,27 @@ type Props = {
shorten?: boolean,
};
-class Time extends React.Component {
- removeEachMinuteCallback: () => void;
+function Time(props: Props) {
+ let content = distanceInWordsToNow(props.dateTime, {
+ addSuffix: props.addSuffix,
+ });
- componentDidMount() {
- this.removeEachMinuteCallback = eachMinute(() => {
- this.forceUpdate();
- });
+ if (props.shorten) {
+ content = content
+ .replace("about", "")
+ .replace("less than a minute ago", "just now")
+ .replace("minute", "min");
}
- componentWillUnmount() {
- this.removeEachMinuteCallback();
- }
-
- render() {
- const { shorten, addSuffix } = this.props;
- let content = distanceInWordsToNow(this.props.dateTime, {
- addSuffix,
- });
-
- if (shorten) {
- content = content
- .replace("about", "")
- .replace("less than a minute ago", "just now")
- .replace("minute", "min");
- }
-
- return (
-
-
-
- );
- }
+ return (
+ {props.children || content}
+ }
+ >
+
+
+ );
}
export default Time;
diff --git a/app/hooks/useUserLocale.js b/app/hooks/useUserLocale.js
new file mode 100644
index 000000000..721fca6e4
--- /dev/null
+++ b/app/hooks/useUserLocale.js
@@ -0,0 +1,12 @@
+// @flow
+import useStores from "./useStores";
+
+export default function useUserLocale() {
+ const { auth } = useStores();
+
+ if (!auth.user) {
+ return undefined;
+ }
+
+ return auth.user.language.split("_")[0];
+}
diff --git a/server/api/__snapshots__/users.test.js.snap b/server/api/__snapshots__/users.test.js.snap
index 48e01865f..859cb70f5 100644
--- a/server/api/__snapshots__/users.test.js.snap
+++ b/server/api/__snapshots__/users.test.js.snap
@@ -9,7 +9,7 @@ Object {
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"isAdmin": false,
"isSuspended": false,
- "language": null,
+ "language": "en_US",
"lastActiveAt": null,
"name": "User 1",
},
@@ -45,7 +45,7 @@ Object {
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"isAdmin": false,
"isSuspended": false,
- "language": null,
+ "language": "en_US",
"lastActiveAt": null,
"name": "User 1",
},
@@ -81,7 +81,7 @@ Object {
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"isAdmin": true,
"isSuspended": false,
- "language": null,
+ "language": "en_US",
"lastActiveAt": null,
"name": "User 1",
},
@@ -126,7 +126,7 @@ Object {
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"isAdmin": false,
"isSuspended": true,
- "language": null,
+ "language": "en_US",
"lastActiveAt": null,
"name": "User 1",
},
diff --git a/server/presenters/__snapshots__/user.test.js.snap b/server/presenters/__snapshots__/user.test.js.snap
index 8f84e53dc..4383924a1 100644
--- a/server/presenters/__snapshots__/user.test.js.snap
+++ b/server/presenters/__snapshots__/user.test.js.snap
@@ -7,7 +7,7 @@ Object {
"id": "123",
"isAdmin": undefined,
"isSuspended": undefined,
- "language": undefined,
+ "language": "en_US",
"lastActiveAt": undefined,
"name": "Test User",
}
@@ -20,7 +20,7 @@ Object {
"id": "123",
"isAdmin": undefined,
"isSuspended": undefined,
- "language": undefined,
+ "language": "en_US",
"lastActiveAt": undefined,
"name": "Test User",
}
diff --git a/server/presenters/user.js b/server/presenters/user.js
index 9f4ed16f3..0b7f75245 100644
--- a/server/presenters/user.js
+++ b/server/presenters/user.js
@@ -24,7 +24,7 @@ export default (user: User, options: Options = {}): ?UserPresentation => {
userData.isAdmin = user.isAdmin;
userData.isSuspended = user.isSuspended;
userData.avatarUrl = user.avatarUrl;
- userData.language = user.language;
+ userData.language = user.language || process.env.DEFAULT_LANGUAGE || "en_US";
if (options.includeDetails) {
userData.email = user.email;
diff --git a/shared/i18n/index.js b/shared/i18n/index.js
index db73bf459..2f42984fa 100644
--- a/shared/i18n/index.js
+++ b/shared/i18n/index.js
@@ -31,6 +31,9 @@ export const initI18n = () => {
return i18n;
};
+// Note: Updating the available languages? Make sure to also update the
+// locales array in app/components/LocaleTime.js to enable translation for timestamps.
+
export const languageOptions = [
{ label: "English (US)", value: "en_US" },
{ label: "Deutsch (Deutschland)", value: "de_DE" },