mirror of
https://github.com/wassname/talk.git
synced 2026-08-06 13:41:02 +08:00
[CORL-219] Common Tweaks (#2460)
* fix: i18n fixes * fix: figma tweaks * fix: tweaked date manipulation logic * fix: linting * fix: linting * fix: tests
This commit is contained in:
committed by
Kim Gardner
parent
7900b02c40
commit
8d78677154
@@ -2,7 +2,7 @@ import cn from "classnames";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { reduceSeconds, UNIT } from "coral-framework/lib/i18n";
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { reduceSeconds } from "coral-framework/lib/i18n";
|
||||
import { reduceSeconds } from "coral-common/helpers/i18n";
|
||||
|
||||
interface From {
|
||||
start: Date;
|
||||
|
||||
@@ -3,12 +3,8 @@ import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
|
||||
import {
|
||||
GetMessage,
|
||||
ScaledUnit,
|
||||
withGetMessage,
|
||||
} from "coral-framework/lib/i18n";
|
||||
|
||||
import { ScaledUnit } from "coral-common/helpers/i18n";
|
||||
import { GetMessage, withGetMessage } from "coral-framework/lib/i18n";
|
||||
import {
|
||||
Button,
|
||||
CheckBox,
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, useCallback, useState } from "react";
|
||||
|
||||
import {
|
||||
GetMessage,
|
||||
ScaledUnit,
|
||||
withGetMessage,
|
||||
} from "coral-framework/lib/i18n";
|
||||
|
||||
import NotAvailable from "coral-admin/components/NotAvailable";
|
||||
import { ScaledUnit } from "coral-common/helpers/i18n";
|
||||
import { GetMessage, withGetMessage } from "coral-framework/lib/i18n";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { ChangeEvent, Component } from "react";
|
||||
|
||||
import { UNIT } from "coral-framework/lib/i18n";
|
||||
import { UNIT } from "coral-common/helpers/i18n";
|
||||
import {
|
||||
Flex,
|
||||
Option,
|
||||
|
||||
@@ -3,8 +3,3 @@ export { default as negotiateLanguages } from "./negotiateLanguages";
|
||||
export { BundledLocales, LoadableLocales, LocalesData } from "./locales";
|
||||
export { default as getMessage } from "./getMessage";
|
||||
export { default as withGetMessage, GetMessage } from "./withGetMessage";
|
||||
export {
|
||||
default as reduceSeconds,
|
||||
UNIT,
|
||||
ScaledUnit,
|
||||
} from "../../../../common/helpers/i18n/reduceSeconds";
|
||||
|
||||
@@ -10,7 +10,7 @@ import React, {
|
||||
import { Field, Form } from "react-final-form";
|
||||
|
||||
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
|
||||
import reduceSeconds, { UNIT } from "coral-common/helpers/i18n/reduceSeconds";
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { ValidationMessage } from "coral-framework/lib/form";
|
||||
import {
|
||||
@@ -146,11 +146,11 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({ viewer }) => {
|
||||
</Box>
|
||||
)}
|
||||
{!showEditForm && (
|
||||
<Flex alignItems="center">
|
||||
<Flex alignItems="baseline">
|
||||
<Typography variant="header2">{viewer.username}</Typography>
|
||||
<Localized id="profile-changeUsername-edit">
|
||||
<Button size="small" color="primary" onClick={toggleEditForm}>
|
||||
Edit
|
||||
edit
|
||||
</Button>
|
||||
</Localized>
|
||||
</Flex>
|
||||
@@ -166,13 +166,17 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({ viewer }) => {
|
||||
</Localized>
|
||||
<Localized
|
||||
id="profile-changeUsername-desc"
|
||||
strong={<strong />}
|
||||
$value={FREQUENCYSCALED.scaled}
|
||||
$unit={FREQUENCYSCALED.unit}
|
||||
>
|
||||
<Typography>
|
||||
Change the username that will appear on all of your past and
|
||||
future comments. Usernames can be changed once every{" "}
|
||||
{FREQUENCYSCALED.scaled} {FREQUENCYSCALED.unit}
|
||||
future comments.{" "}
|
||||
<strong>
|
||||
Usernames can be changed once every {FREQUENCYSCALED.scaled}{" "}
|
||||
{FREQUENCYSCALED.unit}.
|
||||
</strong>
|
||||
</Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
@@ -260,10 +264,15 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({ viewer }) => {
|
||||
Cancel
|
||||
</Button>
|
||||
</Localized>
|
||||
<Localized id="profile-changeUsername-submit">
|
||||
<Localized
|
||||
id="profile-changeUsername-submit"
|
||||
ButtonIcon={<ButtonIcon>save</ButtonIcon>}
|
||||
span={<span />}
|
||||
>
|
||||
<Button
|
||||
variant={pristine || invalid ? "outlined" : "filled"}
|
||||
type="submit"
|
||||
data-testid="profile-changeUsername-save"
|
||||
color={pristine || invalid ? "regular" : "primary"}
|
||||
disabled={pristine || invalid}
|
||||
>
|
||||
|
||||
@@ -3,8 +3,8 @@ import React, { FunctionComponent, useCallback } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { DOWNLOAD_LIMIT_TIMEFRAME } from "coral-common/constants";
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import { useCoralContext } from "coral-framework/lib/bootstrap";
|
||||
import { reduceSeconds, UNIT } from "coral-framework/lib/i18n";
|
||||
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
|
||||
import { DownloadCommentsContainer_viewer } from "coral-stream/__generated__/DownloadCommentsContainer_viewer.graphql";
|
||||
import { Button, CallOut, Icon, Typography } from "coral-ui/components";
|
||||
|
||||
@@ -70,7 +70,7 @@ exports[`renders the empty settings pane 1`] = `
|
||||
data-testid="profile-changeUsername"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-alignCenter"
|
||||
className="Box-root Flex-root Flex-flex Flex-alignBaseline"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-header2 Typography-colorTextPrimary"
|
||||
@@ -87,7 +87,7 @@ exports[`renders the empty settings pane 1`] = `
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Edit
|
||||
edit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@ describe("with recently changed username", () => {
|
||||
within(testRenderer.root).queryByTestID("profile-changeUsername")
|
||||
);
|
||||
within(changeUsername).getByText("u_changed");
|
||||
const editButton = within(changeUsername).getByText("Edit");
|
||||
const editButton = within(changeUsername).getByText("edit");
|
||||
act(() => {
|
||||
editButton.props.onClick();
|
||||
});
|
||||
@@ -102,7 +102,7 @@ describe("with new username", () => {
|
||||
);
|
||||
|
||||
within(changeUsername).getByText("u_original");
|
||||
const editButton = within(changeUsername).getByText("Edit");
|
||||
const editButton = within(changeUsername).getByText("edit");
|
||||
act(() => {
|
||||
editButton.props.onClick();
|
||||
});
|
||||
@@ -145,7 +145,7 @@ describe("change username form", () => {
|
||||
const changeUsername = within(testRenderer.root).getByTestID(
|
||||
"profile-changeUsername"
|
||||
);
|
||||
const editButton = within(changeUsername).getByText("Edit");
|
||||
const editButton = within(changeUsername).getByText("edit");
|
||||
act(() => {
|
||||
editButton.props.onClick();
|
||||
});
|
||||
@@ -156,7 +156,9 @@ describe("change username form", () => {
|
||||
within(changeUsername).getAllByText("This field is required", {
|
||||
exact: false,
|
||||
});
|
||||
const button = within(changeUsername).getByText("Save");
|
||||
const button = within(changeUsername).getByTestID(
|
||||
"profile-changeUsername-save"
|
||||
);
|
||||
expect(button.props.disabled).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -164,7 +166,7 @@ describe("change username form", () => {
|
||||
const changeUsername = within(testRenderer.root).getByTestID(
|
||||
"profile-changeUsername"
|
||||
);
|
||||
const editButton = within(changeUsername).getByText("Edit");
|
||||
const editButton = within(changeUsername).getByText("edit");
|
||||
act(() => {
|
||||
editButton.props.onClick();
|
||||
});
|
||||
@@ -181,7 +183,9 @@ describe("change username form", () => {
|
||||
within(changeUsername).getByText("Usernames do not match. Try again.", {
|
||||
exact: false,
|
||||
});
|
||||
const button = within(changeUsername).getByText("Save");
|
||||
const button = within(changeUsername).getByTestID(
|
||||
"profile-changeUsername-save"
|
||||
);
|
||||
expect(button.props.disabled).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -189,7 +193,7 @@ describe("change username form", () => {
|
||||
const changeUsername = within(testRenderer.root).getByTestID(
|
||||
"profile-changeUsername"
|
||||
);
|
||||
const editButton = within(changeUsername).getByText("Edit");
|
||||
const editButton = within(changeUsername).getByText("edit");
|
||||
act(() => {
|
||||
editButton.props.onClick();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./locales";
|
||||
export { default as reduceSeconds } from "./reduceSeconds";
|
||||
export * from "./reduceSeconds";
|
||||
@@ -6,7 +6,7 @@ import { VError } from "verror";
|
||||
|
||||
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
|
||||
import { ERROR_CODES, ERROR_TYPES } from "coral-common/errors";
|
||||
import reduceSeconds, { UNIT } from "coral-common/helpers/i18n/reduceSeconds";
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import { translate } from "coral-server/services/i18n";
|
||||
|
||||
import { Writeable } from "coral-common/types";
|
||||
|
||||
@@ -128,7 +128,8 @@ export const Users = (ctx: TenantContext) => ({
|
||||
ctx.mailerQueue,
|
||||
ctx.tenant,
|
||||
ctx.user!,
|
||||
input.username
|
||||
input.username,
|
||||
ctx.now
|
||||
),
|
||||
updateUserUsername: async (input: GQLUpdateUserUsernameInput) =>
|
||||
updateUsernameByID(
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "path";
|
||||
|
||||
import { LanguageCode, LOCALES } from "coral-common/helpers/i18n/locales";
|
||||
import config from "coral-server/config";
|
||||
import logger from "coral-server/logger";
|
||||
|
||||
/**
|
||||
* isLanguageCode will return true if the string is a `LanguageCode`.
|
||||
@@ -14,8 +15,13 @@ function isLanguageCode(locale: string): locale is LanguageCode {
|
||||
return LOCALES.some(code => code === locale);
|
||||
}
|
||||
|
||||
// pathToLocales is the path where the server stores the locales.
|
||||
const pathToLocales = path.join(__dirname, "..", "..", "locales");
|
||||
// pathsToLocales is the paths where to find the locales.
|
||||
const pathsToLocales = [
|
||||
// Client locales.
|
||||
path.join(__dirname, "..", "..", "..", "..", "locales"),
|
||||
// Server locales.
|
||||
path.join(__dirname, "..", "..", "locales"),
|
||||
];
|
||||
|
||||
export class I18n {
|
||||
private bundles: Partial<Record<LanguageCode, FluentBundle>> = {};
|
||||
@@ -29,35 +35,36 @@ export class I18n {
|
||||
* load will read all the translations located in the server locales folder.
|
||||
*/
|
||||
public async load() {
|
||||
// Load all the locales from the server locales folder.
|
||||
|
||||
// Load all the locales from the locales folders.
|
||||
const folders = await fs.readdir(pathToLocales);
|
||||
for (const localesFolder of pathsToLocales) {
|
||||
const folders = await fs.readdir(localesFolder);
|
||||
|
||||
// Load all the translation files for each of the folders.
|
||||
for (const folder of folders) {
|
||||
// Parse out the language code.
|
||||
const locale = path.basename(folder);
|
||||
if (!isLanguageCode(locale)) {
|
||||
throw new Error(`invalid language code: ${locale}`);
|
||||
// Load all the translation files for each of the folders.
|
||||
for (const folder of folders) {
|
||||
// Parse out the language code.
|
||||
const locale = path.basename(folder);
|
||||
if (!isLanguageCode(locale)) {
|
||||
throw new Error(`invalid language code: ${locale}`);
|
||||
}
|
||||
|
||||
// Now we have a language code.
|
||||
const bundle: FluentBundle =
|
||||
this.bundles[locale] || new FluentBundle(locale);
|
||||
|
||||
// Load all the translations in the folder.
|
||||
const files = await fs.readdir(path.join(localesFolder, folder));
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(localesFolder, folder, file);
|
||||
logger.debug({ locale, filePath }, "loading messages for locale");
|
||||
|
||||
const messages = await fs.readFile(filePath, "utf8");
|
||||
|
||||
bundle.addMessages(messages);
|
||||
}
|
||||
|
||||
this.bundles[locale] = bundle;
|
||||
}
|
||||
|
||||
// Now we have a language code.
|
||||
const bundle = new FluentBundle(locale);
|
||||
|
||||
// Load all the translations in the folder.
|
||||
const files = await fs.readdir(path.join(pathToLocales, folder));
|
||||
|
||||
for (const file of files) {
|
||||
const messages = await fs.readFile(
|
||||
path.join(pathToLocales, folder, file),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
bundle.addMessages(messages);
|
||||
}
|
||||
|
||||
this.bundles[locale] = bundle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -382,20 +382,24 @@ export async function updateUsername(
|
||||
mailer: MailerQueue,
|
||||
tenant: Tenant,
|
||||
user: User,
|
||||
username: string
|
||||
username: string,
|
||||
now: Date
|
||||
) {
|
||||
// Validate the username.
|
||||
validateUsername(username);
|
||||
|
||||
const lastUsernameEditAllowed = new Date();
|
||||
const dateDiff =
|
||||
lastUsernameEditAllowed.getSeconds() - ALLOWED_USERNAME_CHANGE_FREQUENCY;
|
||||
lastUsernameEditAllowed.setDate(dateDiff);
|
||||
// Get the earliest date that the username could have been edited before to/
|
||||
// allow it now.
|
||||
const lastUsernameEditAllowed = DateTime.fromJSDate(now)
|
||||
.plus({ seconds: -ALLOWED_USERNAME_CHANGE_FREQUENCY })
|
||||
.toJSDate();
|
||||
|
||||
const { history } = user.status.username;
|
||||
if (history.length > 1) {
|
||||
// If the last update was made at a date sooner than the earliest edited
|
||||
// date, then we know that the last edit was conducted within the time-frame
|
||||
// already.
|
||||
const lastUpdate = history[history.length - 1];
|
||||
|
||||
if (lastUpdate.createdAt > lastUsernameEditAllowed) {
|
||||
throw new UsernameUpdatedWithinWindowError(lastUpdate.createdAt);
|
||||
}
|
||||
|
||||
@@ -221,14 +221,14 @@ configure-configureQuery-storyNotFound = Story not found
|
||||
|
||||
## Change username
|
||||
profile-changeUsername-success = Your username has been successfully updated
|
||||
profile-changeUsername-edit = Edit
|
||||
profile-changeUsername-edit = edit
|
||||
profile-changeUsername-heading = Edit your username
|
||||
profile-changeUsername-desc = Change the username that will appear on all of your past and future comments. Usernames can be changed once every { framework-timeago-time }.
|
||||
profile-changeUsername-desc = Change the username that will appear on all of your past and future comments. <strong>Usernames can be changed once every { framework-timeago-time }.</strong>
|
||||
profile-changeUsername-current = Current username
|
||||
profile-changeUsername-newUsername-label = New username
|
||||
profile-changeUsername-confirmNewUsername-label = Confirm new username
|
||||
profile-changeUsername-cancel = Cancel
|
||||
profile-changeUsername-submit = Save
|
||||
profile-changeUsername-submit = <ButtonIcon>save</ButtonIcon> <span>Save</span>
|
||||
profile-changeUsername-recentChange = Your username has been changed in the last { framework-timeago-time }. You may change your username again on { $nextUpdate }
|
||||
profile-changeUsername-close = Close
|
||||
|
||||
|
||||
Reference in New Issue
Block a user