From bfbd15a55605505da7753a664f8257b243f28d18 Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Sat, 16 Mar 2024 15:42:48 -0400 Subject: [PATCH 1/5] feat: locales editing page --- packages/locales/lib/human/en.json | 15 ++- packages/locales/lib/index.js | 8 +- packages/locales/lib/missing.js | 43 +++++--- server/src/graphql/resolvers.js | 11 ++ server/src/graphql/typeDefs/index.graphql | 1 + server/src/graphql/typeDefs/map.graphql | 6 + src/assets/css/main.css | 6 + src/components/virtual/Table.jsx | 31 ++++++ src/pages/index.jsx | 3 + src/pages/locales/components/AllSwitch.jsx | 22 ++++ src/pages/locales/components/EditLocale.jsx | 34 ++++++ .../locales/components/LocalesFooter.jsx | 35 ++++++ .../locales/components/LocalesHeader.jsx | 51 +++++++++ src/pages/locales/components/LocalesTable.jsx | 103 ++++++++++++++++++ src/pages/locales/hooks/store.js | 35 ++++++ src/pages/locales/index.jsx | 20 ++++ src/services/queries/config.js | 10 ++ src/utils/downloadJson.js | 4 +- 18 files changed, 417 insertions(+), 21 deletions(-) create mode 100644 src/components/virtual/Table.jsx create mode 100644 src/pages/locales/components/AllSwitch.jsx create mode 100644 src/pages/locales/components/EditLocale.jsx create mode 100644 src/pages/locales/components/LocalesFooter.jsx create mode 100644 src/pages/locales/components/LocalesHeader.jsx create mode 100644 src/pages/locales/components/LocalesTable.jsx create mode 100644 src/pages/locales/hooks/store.js create mode 100644 src/pages/locales/index.jsx diff --git a/packages/locales/lib/human/en.json b/packages/locales/lib/human/en.json index 6eca60e3f..6977fce0e 100644 --- a/packages/locales/lib/human/en.json +++ b/packages/locales/lib/human/en.json @@ -765,5 +765,18 @@ "reset_spawnpoints": "Reset Spawnpoints", "reset_submission_cells": "Reset Submission Cells", "hisuian": "Hisuian", - "spacial_rend_range": "Spacial Rend Range" + "spacial_rend_range": "Spacial Rend Range", + "key": "Key", + "ai": "AI", + "human": "Human", + "locales": "Locales", + "instructions": "Instructions", + "locale_instructions_1": "Select a language from the dropdown", + "locale_instructions_2": "Enter the desired translations in the \"Human\" column", + "locale_instructions_3": "Click the \"$t(download)\" button to download a JSON file", + "locale_instructions_4": "Fork the GitHub repo link below", + "locale_instructions_5": "Create a new branch and name it the language you are translating", + "locale_instructions_6": "Replace the contents of \"packages/locales/lib/human/{{lng}}.json\" with the file you downloaded", + "locale_instructions_7": "Create a pull request", + "locale_instructions_8": "Wait for the pull request to be reviewed and merged" } diff --git a/packages/locales/lib/index.js b/packages/locales/lib/index.js index 7f66f015c..900005dde 100644 --- a/packages/locales/lib/index.js +++ b/packages/locales/lib/index.js @@ -1,7 +1,12 @@ const { create } = require('./create') const { missing } = require('./missing') const { generate } = require('./generate') -const { readLocaleDirectory, writeAll, getStatus } = require('./utils') +const { + readLocaleDirectory, + writeAll, + getStatus, + readAndParseJson, +} = require('./utils') const locales = readLocaleDirectory(true).map((x) => x.replace('.json', '')) const status = getStatus() @@ -12,3 +17,4 @@ module.exports.create = create module.exports.missing = missing module.exports.generate = generate module.exports.writeAll = writeAll +module.exports.readAndParseJson = readAndParseJson diff --git a/packages/locales/lib/missing.js b/packages/locales/lib/missing.js index ed6591b22..bec46ef0b 100644 --- a/packages/locales/lib/missing.js +++ b/packages/locales/lib/missing.js @@ -5,27 +5,34 @@ const { resolve } = require('path') const { log, HELPERS } = require('@rm/logger') const { readAndParseJson, readLocaleDirectory } = require('./utils') -async function missing() { - const localTranslations = readLocaleDirectory(true) +/** + * + * @param {string} fileName + * @returns {Promise} + */ +async function missing(fileName) { const englishRef = await readAndParseJson('en.json', true) + const humanLocales = await readAndParseJson(fileName, true) + /** @type {import('./generate').I18nObject} */ + const missingKeys = {} - await Promise.allSettled( - localTranslations.map(async (fileName) => { - const humanLocales = await readAndParseJson(fileName, true) - const aiLocales = await readAndParseJson(fileName, false) - const combined = { - ...aiLocales, - ...humanLocales, + Object.keys(englishRef) + .sort() + .forEach((key) => { + if (!humanLocales[key] && !key.startsWith('locale_selection_')) { + missingKeys[key] = process.argv.includes('--ally') + ? `t('${key}')` + : englishRef[key] } - const missingKeys = {} + }) + return missingKeys +} - Object.keys(englishRef).forEach((key) => { - if (!combined[key] && !key.startsWith('locale_selection_')) { - missingKeys[key] = process.argv.includes('--ally') - ? `t('${key}')` - : englishRef[key] - } - }) +async function missingAll() { + const localTranslations = readLocaleDirectory(true) + await Promise.allSettled( + localTranslations.map(async (fileName) => { + const missingKeys = await missing(fileName) await fs.writeFile( resolve( __dirname, @@ -44,5 +51,5 @@ async function missing() { module.exports.missing = missing if (require.main === module) { - missing().then(() => process.exit(0)) + missingAll().then(() => process.exit(0)) } diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index e415a1f21..0b4ad74fa 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -3,6 +3,7 @@ const { resolve } = require('path') const { GraphQLJSON } = require('graphql-type-json') const { S2LatLng, S2RegionCoverer, S2LatLngRect } = require('nodes2ts') const config = require('@rm/config') +const { missing, readAndParseJson } = require('@rm/locales') const buildDefaultFilters = require('../services/filters/builder/base') const filterComponents = require('../services/functions/filterComponents') @@ -198,6 +199,16 @@ const resolvers = { } return {} }, + locales: async (_, { locale }) => { + const missingLocales = await missing(`${locale}.json`) + return locale + ? { + missing: Object.keys(missingLocales), + human: await readAndParseJson(`${locale}.json`, true), + ai: await readAndParseJson(`${locale}.json`, false), + } + : { missing: null, human: null, ai: null } + }, motdCheck: (_, { clientIndex }, { req, perms }) => { const motd = config.getMapConfig(req).messageOfTheDay return ( diff --git a/server/src/graphql/typeDefs/index.graphql b/server/src/graphql/typeDefs/index.graphql index db050f00c..6afeb5b2a 100644 --- a/server/src/graphql/typeDefs/index.graphql +++ b/server/src/graphql/typeDefs/index.graphql @@ -23,6 +23,7 @@ type Query { filters: JSON ): [Gym] gymsSingle(id: ID, perm: String): Gym + locales(locale: String): Locales motdCheck(clientIndex: Int): Boolean nests( minLat: Float diff --git a/server/src/graphql/typeDefs/map.graphql b/server/src/graphql/typeDefs/map.graphql index eb4c59d0e..5840c517e 100644 --- a/server/src/graphql/typeDefs/map.graphql +++ b/server/src/graphql/typeDefs/map.graphql @@ -216,3 +216,9 @@ type ValidUserObj { loggedIn: Boolean admin: Boolean } + +type Locales { + human: JSON + ai: JSON + missing: JSON +} diff --git a/src/assets/css/main.css b/src/assets/css/main.css index f7025d4ae..d5d6cce69 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -554,3 +554,9 @@ input[type='time']::-webkit-calendar-picker-indicator { opacity: 90%; z-index: 9; } + +.locales-layout { + display: grid; + grid-template-rows: auto 1fr auto; /* Header, table, footer */ + min-height: 100vh; +} diff --git a/src/components/virtual/Table.jsx b/src/components/virtual/Table.jsx new file mode 100644 index 000000000..eacb45b32 --- /dev/null +++ b/src/components/virtual/Table.jsx @@ -0,0 +1,31 @@ +import * as React from 'react' +import Table from '@mui/material/Table' +import TableBody from '@mui/material/TableBody' +import TableContainer from '@mui/material/TableContainer' +import TableHead from '@mui/material/TableHead' +import TableRow from '@mui/material/TableRow' +import Paper from '@mui/material/Paper' +import { TableVirtuoso } from 'react-virtuoso' + +const VirtuosoTableComponents = { + Scroller: React.forwardRef((props, ref) => ( + + )), + Table: (props) => ( + + ), + TableHead, + // eslint-disable-next-line no-unused-vars + TableRow: ({ item: _, ...props }) => , + TableBody: React.forwardRef((props, ref) => ( + + )), +} + +/** @param {import('react-virtuoso').TableVirtuosoProps} props */ +export function VirtualTable(props) { + return +} diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 26c598da4..a425a21be 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -10,6 +10,7 @@ import { BlockedPage } from './Blocked' import { ErrorPage } from './Error' import { DataManagerPage } from './data' import { ResetPage } from './Reset' +import { LocalesPage } from './locales' const Playground = React.lazy(() => import('./playground').then(({ PlaygroundPage }) => ({ @@ -44,6 +45,7 @@ const playgroundRoute = ( ) const errorRoute = const resetRoute = +const localesPage = export function Pages() { return ( @@ -52,6 +54,7 @@ export function Pages() { + diff --git a/src/pages/locales/components/AllSwitch.jsx b/src/pages/locales/components/AllSwitch.jsx new file mode 100644 index 000000000..677e6c357 --- /dev/null +++ b/src/pages/locales/components/AllSwitch.jsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import FormControlLabel from '@mui/material/FormControlLabel' +import Switch from '@mui/material/Switch' +import { useTranslation } from 'react-i18next' + +import { useLocalesStore } from '../hooks/store' + +export function AllSwitch() { + const { t } = useTranslation() + const all = useLocalesStore((s) => s.all) + return ( + useLocalesStore.setState({ all: checked })} + /> + } + label={t('all')} + /> + ) +} diff --git a/src/pages/locales/components/EditLocale.jsx b/src/pages/locales/components/EditLocale.jsx new file mode 100644 index 000000000..98d299faf --- /dev/null +++ b/src/pages/locales/components/EditLocale.jsx @@ -0,0 +1,34 @@ +// @ts-check +import * as React from 'react' +import TextField from '@mui/material/TextField' +import { useLocalesStore } from '../hooks/store' + +/** @param {{ name: string } & import('@mui/material').TextFieldProps} props */ +export function EditLocale({ name, type, ...props }) { + const value = useLocalesStore((s) => s.custom[name] || '') + const isScrolling = useLocalesStore((s) => s.isScrolling) + /** @type {import('@mui/material').TextFieldProps['onChange']} */ + const onChange = React.useCallback( + (event) => { + useLocalesStore.setState((prev) => ({ + custom: { + ...prev.custom, + [name]: + type === 'number' ? +event.target.value || 0 : event.target.value, + }, + })) + }, + [name], + ) + return ( + + ) +} diff --git a/src/pages/locales/components/LocalesFooter.jsx b/src/pages/locales/components/LocalesFooter.jsx new file mode 100644 index 000000000..f575519da --- /dev/null +++ b/src/pages/locales/components/LocalesFooter.jsx @@ -0,0 +1,35 @@ +// @ts-check +import * as React from 'react' +import Grid from '@mui/material/Unstable_Grid2' +import Button from '@mui/material/Button' +import GitHubIcon from '@mui/icons-material/GitHub' +import { useTranslation } from 'react-i18next' + +import { downloadLocales } from '../hooks/store' +import { AllSwitch } from './AllSwitch' + +const github = + +export function LocalesFooter() { + const { t } = useTranslation() + return ( + + + + + + + + + + + + ) +} diff --git a/src/pages/locales/components/LocalesHeader.jsx b/src/pages/locales/components/LocalesHeader.jsx new file mode 100644 index 000000000..e2ec6e688 --- /dev/null +++ b/src/pages/locales/components/LocalesHeader.jsx @@ -0,0 +1,51 @@ +// @ts-check +import * as React from 'react' +import Grid from '@mui/material/Unstable_Grid2' +import Typography from '@mui/material/Typography' +import Collapse from '@mui/material/Collapse' +import Button from '@mui/material/Button' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import { useTranslation } from 'react-i18next' + +import { LocaleSelection } from '@components/inputs/LocaleSelection' + +import { useLocalesStore } from '../hooks/store' + +const expandMore = + +export function LocalesHeader() { + const { t, i18n } = useTranslation() + const instructions = useLocalesStore((s) => s.instructions) + return ( + + + {t('locales')} + + + + + + + + +
    + {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => ( + + {t(`locale_instructions_${i}`, { lng: i18n.language })} + + ))} +
+
+
+ ) +} diff --git a/src/pages/locales/components/LocalesTable.jsx b/src/pages/locales/components/LocalesTable.jsx new file mode 100644 index 000000000..db23b6dcd --- /dev/null +++ b/src/pages/locales/components/LocalesTable.jsx @@ -0,0 +1,103 @@ +// @ts-check +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { useQuery } from '@apollo/client' +import TableCell from '@mui/material/TableCell' +import TableRow from '@mui/material/TableRow' +import Box from '@mui/material/Box' + +import { LOCALES_STATUS } from '@services/queries/config' +import { VirtualTable } from '@components/virtual/Table' + +import { setScrolling, useLocalesStore } from '../hooks/store' +import { EditLocale } from './EditLocale' + +/** @type {import('react-virtuoso').TableVirtuosoProps['fixedHeaderContent']} */ +function fixedHeaderContent() { + const { t } = useTranslation() + return ( + + {t('key')} + {t('locale_selection_en')} + {t('ai')} + {t('human')} + + ) +} + +/** @type {import('react-virtuoso').TableVirtuosoProps['itemContent']} */ +function itemContent(_index, row) { + return ( + <> + {row.name} + {row.english} + {row.ai} + + + + + ) +} + +export function LocalesTable() { + const { i18n } = useTranslation() + const all = useLocalesStore((s) => s.all) + + const { data, loading } = useQuery(LOCALES_STATUS, { + fetchPolicy: 'network-only', + nextFetchPolicy: 'cache-only', + variables: { locale: i18n.language }, + }) + + const { data: enData, loading: enLoading } = useQuery(LOCALES_STATUS, { + fetchPolicy: 'network-only', + nextFetchPolicy: 'cache-only', + variables: { locale: 'en' }, + }) + + const stringSorter = new Intl.Collator(i18n.language, { + sensitivity: 'base', + ignorePunctuation: true, + }) + + const rows = React.useMemo(() => { + if (data?.locales && enData?.locales) { + const { missing, ai } = data.locales + /** @type {string[]} */ + const source = all ? Object.keys(enData.locales.human) : missing + return source.toSorted(stringSorter.compare).map((key) => ({ + name: key, + english: enData.locales.human[key], + ai: ai[key], + missing: !!missing[key], + type: typeof enData.locales.human[key], + })) + } + return [] + }, [data, enData, all]) + + React.useEffect(() => { + if (Array.isArray(data?.locales?.missing)) { + useLocalesStore.setState({ + custom: all + ? data?.locales.human + : Object.fromEntries(data.locales.missing.map((key) => [key, ''])), + existingHuman: data.locales.human || {}, + }) + } + }, [data, all]) + + return ( + + + + ) +} diff --git a/src/pages/locales/hooks/store.js b/src/pages/locales/hooks/store.js new file mode 100644 index 000000000..5462b3f0d --- /dev/null +++ b/src/pages/locales/hooks/store.js @@ -0,0 +1,35 @@ +// @ts-check +import { create } from 'zustand' + +import { downloadJson } from '@utils/downloadJson' + +/** + * @typedef {{ + * custom: Record + * existingHuman: Record + * all: boolean + * instructions: boolean + * isScrolling: boolean + * }} LocalesStore + * @type {import("zustand").UseBoundStore>} + */ +export const useLocalesStore = create(() => ({ + custom: {}, + existingHuman: {}, + all: false, + instructions: false, + isScrolling: false, +})) + +export const downloadLocales = () => { + const { custom, existingHuman } = useLocalesStore.getState() + const locale = localStorage.getItem('i18nextLng') || 'en' + const filtered = Object.fromEntries( + Object.entries(custom).filter(([, v]) => v !== ''), + ) + return downloadJson({ ...existingHuman, ...filtered }, `${locale}.json`) +} + +/** @param {boolean} isScrolling */ +export const setScrolling = (isScrolling) => + useLocalesStore.setState({ isScrolling }) diff --git a/src/pages/locales/index.jsx b/src/pages/locales/index.jsx new file mode 100644 index 000000000..1e3921407 --- /dev/null +++ b/src/pages/locales/index.jsx @@ -0,0 +1,20 @@ +// @ts-check +import * as React from 'react' +import Box from '@mui/material/Box' + +import { useHideElement } from '@hooks/useHideElement' + +import { LocalesTable } from './components/LocalesTable' +import { LocalesHeader } from './components/LocalesHeader' +import { LocalesFooter } from './components/LocalesFooter' + +export function LocalesPage() { + useHideElement() + return ( + + + + + + ) +} diff --git a/src/services/queries/config.js b/src/services/queries/config.js index bcfe63d68..da225bec6 100644 --- a/src/services/queries/config.js +++ b/src/services/queries/config.js @@ -51,3 +51,13 @@ export const SAVE_COMPONENT = gql` saveComponent(component: $component, code: $code) } ` + +export const LOCALES_STATUS = gql` + query Locales($locale: String!) { + locales(locale: $locale) { + human + ai + missing + } + } +` diff --git a/src/utils/downloadJson.js b/src/utils/downloadJson.js index 9cbb135e6..cb15218fc 100644 --- a/src/utils/downloadJson.js +++ b/src/utils/downloadJson.js @@ -9,7 +9,9 @@ export function downloadJson(json, fileName) { const el = document.createElement('a') el.setAttribute( 'href', - `data:application/json;charset=utf-8,${encodeURIComponent(json)}`, + `data:application/json;charset=utf-8,${encodeURIComponent( + typeof json === 'string' ? json : JSON.stringify(json, null, 2), + )}`, ) el.setAttribute('download', fileName) el.style.display = 'none' From 77eafa91c492e6a25ce63918ad46226899b96d6c Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Sat, 16 Mar 2024 16:34:04 -0400 Subject: [PATCH 2/5] fix: server side routes --- server/src/routes/clientRouter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/routes/clientRouter.js b/server/src/routes/clientRouter.js index 601b781b6..703774df2 100644 --- a/server/src/routes/clientRouter.js +++ b/server/src/routes/clientRouter.js @@ -15,6 +15,8 @@ const CLIENT_ROUTES = [ '/500', '/reset', '/playground', + '/locales', + '/data-management', ] router.get(CLIENT_ROUTES, (req, res) => { From 4ed05ff5d53435880d7388eaa11faac69a0ab77f Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Sat, 16 Mar 2024 16:54:11 -0400 Subject: [PATCH 3/5] fix: rounding & icon for missing ai --- packages/locales/lib/utils.js | 10 +++++----- server/src/index.js | 7 ++++--- src/pages/locales/components/LocalesTable.jsx | 7 +++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/locales/lib/utils.js b/packages/locales/lib/utils.js index d58f9b69d..d2d80d167 100644 --- a/packages/locales/lib/utils.js +++ b/packages/locales/lib/utils.js @@ -132,14 +132,14 @@ function getStatus() { ), ) const mergedSize = Object.keys({ ...aiJson, ...humanJson }).length - const human = +(humanHas / total).toFixed(2) * 100 - const localeTotal = Math.min(1, +(mergedSize / total).toFixed(2)) * 100 + const human = (humanHas / total) * 100 + const localeTotal = (mergedSize / total) * 100 return [ locale.replace('.json', ''), { - human, - ai: localeTotal - human, - total: localeTotal, + human: +human.toFixed(2), + ai: +(localeTotal - human).toFixed(2), + total: +localeTotal.toFixed(2), }, ] }), diff --git a/server/src/index.js b/server/src/index.js index 4d1bc854f..3a2459ac8 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -244,12 +244,13 @@ startApollo(httpServer).then((server) => { const definition = parse(req.body.query).definitions.find( (d) => d.kind === 'OperationDefinition', ) + const endpoint = definition?.name?.value || '' const errorCtx = { id, user, clientV, serverV, - endpoint: definition.name?.value || '', + endpoint, } if (clientV && serverV && clientV !== serverV) { @@ -262,7 +263,7 @@ startApollo(httpServer).then((server) => { }) } - if (!perms) { + if (!perms && endpoint !== 'Locales') { throw new GraphQLError('session_expired', { extensions: { ...errorCtx, @@ -275,7 +276,7 @@ startApollo(httpServer).then((server) => { if ( definition?.operation === 'mutation' && !id && - definition?.name?.value !== 'SetTutorial' + endpoint !== 'SetTutorial' ) { throw new GraphQLError('unauthenticated', { extensions: { diff --git a/src/pages/locales/components/LocalesTable.jsx b/src/pages/locales/components/LocalesTable.jsx index db23b6dcd..a7c87c104 100644 --- a/src/pages/locales/components/LocalesTable.jsx +++ b/src/pages/locales/components/LocalesTable.jsx @@ -5,6 +5,7 @@ import { useQuery } from '@apollo/client' import TableCell from '@mui/material/TableCell' import TableRow from '@mui/material/TableRow' import Box from '@mui/material/Box' +import ClearIcon from '@mui/icons-material/Clear' import { LOCALES_STATUS } from '@services/queries/config' import { VirtualTable } from '@components/virtual/Table' @@ -12,6 +13,8 @@ import { VirtualTable } from '@components/virtual/Table' import { setScrolling, useLocalesStore } from '../hooks/store' import { EditLocale } from './EditLocale' +const clear = + /** @type {import('react-virtuoso').TableVirtuosoProps['fixedHeaderContent']} */ function fixedHeaderContent() { const { t } = useTranslation() @@ -30,8 +33,8 @@ function itemContent(_index, row) { return ( <> {row.name} - {row.english} - {row.ai} + {row.english || clear} + {row.ai || clear} Date: Sat, 16 Mar 2024 16:56:30 -0400 Subject: [PATCH 4/5] fix: no floats --- packages/locales/lib/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/locales/lib/utils.js b/packages/locales/lib/utils.js index d2d80d167..14a150917 100644 --- a/packages/locales/lib/utils.js +++ b/packages/locales/lib/utils.js @@ -137,9 +137,9 @@ function getStatus() { return [ locale.replace('.json', ''), { - human: +human.toFixed(2), - ai: +(localeTotal - human).toFixed(2), - total: +localeTotal.toFixed(2), + human: Math.round(human), + ai: Math.round(localeTotal - human), + total: Math.round(localeTotal), }, ] }), From b259e4274cab22bfee450ef950b3c54616dca1e7 Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Sat, 16 Mar 2024 18:39:31 -0400 Subject: [PATCH 5/5] fix: mobile friendly :| --- packages/locales/lib/human/en.json | 3 +- src/assets/css/main.css | 2 +- src/components/virtual/Table.jsx | 4 +- src/pages/locales/components/EditLocale.jsx | 7 ++- .../locales/components/LocalesHeader.jsx | 4 +- src/pages/locales/components/LocalesTable.jsx | 46 ++++++++++++++++--- src/pages/locales/hooks/store.js | 5 -- 7 files changed, 51 insertions(+), 20 deletions(-) diff --git a/packages/locales/lib/human/en.json b/packages/locales/lib/human/en.json index 6977fce0e..3dbef2e01 100644 --- a/packages/locales/lib/human/en.json +++ b/packages/locales/lib/human/en.json @@ -778,5 +778,6 @@ "locale_instructions_5": "Create a new branch and name it the language you are translating", "locale_instructions_6": "Replace the contents of \"packages/locales/lib/human/{{lng}}.json\" with the file you downloaded", "locale_instructions_7": "Create a pull request", - "locale_instructions_8": "Wait for the pull request to be reviewed and merged" + "locale_instructions_8": "Wait for the pull request to be reviewed and merged", + "enter_translation": "Enter Translation" } diff --git a/src/assets/css/main.css b/src/assets/css/main.css index d5d6cce69..f79e748ae 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -558,5 +558,5 @@ input[type='time']::-webkit-calendar-picker-indicator { .locales-layout { display: grid; grid-template-rows: auto 1fr auto; /* Header, table, footer */ - min-height: 100vh; + min-height: 100svh; } diff --git a/src/components/virtual/Table.jsx b/src/components/virtual/Table.jsx index eacb45b32..590e7b124 100644 --- a/src/components/virtual/Table.jsx +++ b/src/components/virtual/Table.jsx @@ -7,7 +7,7 @@ import TableRow from '@mui/material/TableRow' import Paper from '@mui/material/Paper' import { TableVirtuoso } from 'react-virtuoso' -const VirtuosoTableComponents = { +const COMPONENTS = { Scroller: React.forwardRef((props, ref) => ( )), @@ -27,5 +27,5 @@ const VirtuosoTableComponents = { /** @param {import('react-virtuoso').TableVirtuosoProps} props */ export function VirtualTable(props) { - return + return } diff --git a/src/pages/locales/components/EditLocale.jsx b/src/pages/locales/components/EditLocale.jsx index 98d299faf..5a2e4878f 100644 --- a/src/pages/locales/components/EditLocale.jsx +++ b/src/pages/locales/components/EditLocale.jsx @@ -1,12 +1,14 @@ // @ts-check import * as React from 'react' import TextField from '@mui/material/TextField' +import { useTranslation } from 'react-i18next' + import { useLocalesStore } from '../hooks/store' /** @param {{ name: string } & import('@mui/material').TextFieldProps} props */ export function EditLocale({ name, type, ...props }) { + const { t } = useTranslation() const value = useLocalesStore((s) => s.custom[name] || '') - const isScrolling = useLocalesStore((s) => s.isScrolling) /** @type {import('@mui/material').TextFieldProps['onChange']} */ const onChange = React.useCallback( (event) => { @@ -26,8 +28,9 @@ export function EditLocale({ name, type, ...props }) { type={type} value={value} onChange={onChange} - multiline={type === 'text' && !isScrolling} + multiline={type === 'text'} size="small" + placeholder={t('enter_translation')} {...props} /> ) diff --git a/src/pages/locales/components/LocalesHeader.jsx b/src/pages/locales/components/LocalesHeader.jsx index e2ec6e688..c0377a810 100644 --- a/src/pages/locales/components/LocalesHeader.jsx +++ b/src/pages/locales/components/LocalesHeader.jsx @@ -18,10 +18,10 @@ export function LocalesHeader() { const instructions = useLocalesStore((s) => s.instructions) return ( - + {t('locales')} - + diff --git a/src/pages/locales/components/LocalesTable.jsx b/src/pages/locales/components/LocalesTable.jsx index a7c87c104..a1e011ef5 100644 --- a/src/pages/locales/components/LocalesTable.jsx +++ b/src/pages/locales/components/LocalesTable.jsx @@ -6,19 +6,34 @@ import TableCell from '@mui/material/TableCell' import TableRow from '@mui/material/TableRow' import Box from '@mui/material/Box' import ClearIcon from '@mui/icons-material/Clear' +import useMediaQuery from '@mui/material/useMediaQuery' +import Grid2 from '@mui/material/Unstable_Grid2' +import Typography from '@mui/material/Typography' +import Divider from '@mui/material/Divider' import { LOCALES_STATUS } from '@services/queries/config' import { VirtualTable } from '@components/virtual/Table' -import { setScrolling, useLocalesStore } from '../hooks/store' +import { useLocalesStore } from '../hooks/store' import { EditLocale } from './EditLocale' +/** + * @typedef {{ + * name: string, + * english?: string, + * ai?: string, + * missing: boolean, + * type: string + * }} Row + */ const clear = /** @type {import('react-virtuoso').TableVirtuosoProps['fixedHeaderContent']} */ function fixedHeaderContent() { const { t } = useTranslation() - return ( + // @ts-ignore + const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm')) + return isMobile ? null : ( {t('key')} {t('locale_selection_en')} @@ -28,11 +43,25 @@ function fixedHeaderContent() { ) } -/** @type {import('react-virtuoso').TableVirtuosoProps['itemContent']} */ -function itemContent(_index, row) { - return ( +/** @type {import('react-virtuoso').TableVirtuosoProps['itemContent']} */ +function itemContent(_index, row, ctx) { + return ctx?.isMobile ? ( + + + {row.name} + + + {row.english} + + + + + ) : ( <> - {row.name} + {row.name} {row.english || clear} {row.ai || clear} @@ -48,6 +77,8 @@ function itemContent(_index, row) { export function LocalesTable() { const { i18n } = useTranslation() const all = useLocalesStore((s) => s.all) + // @ts-ignore + const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm')) const { data, loading } = useQuery(LOCALES_STATUS, { fetchPolicy: 'network-only', @@ -66,6 +97,7 @@ export function LocalesTable() { ignorePunctuation: true, }) + /** @type {Row[]} */ const rows = React.useMemo(() => { if (data?.locales && enData?.locales) { const { missing, ai } = data.locales @@ -99,7 +131,7 @@ export function LocalesTable() { data={loading || enLoading ? [] : rows} fixedHeaderContent={fixedHeaderContent} itemContent={itemContent} - isScrolling={setScrolling} + context={{ isMobile }} /> ) diff --git a/src/pages/locales/hooks/store.js b/src/pages/locales/hooks/store.js index 5462b3f0d..f3fd2ccdc 100644 --- a/src/pages/locales/hooks/store.js +++ b/src/pages/locales/hooks/store.js @@ -9,7 +9,6 @@ import { downloadJson } from '@utils/downloadJson' * existingHuman: Record * all: boolean * instructions: boolean - * isScrolling: boolean * }} LocalesStore * @type {import("zustand").UseBoundStore>} */ @@ -29,7 +28,3 @@ export const downloadLocales = () => { ) return downloadJson({ ...existingHuman, ...filtered }, `${locale}.json`) } - -/** @param {boolean} isScrolling */ -export const setScrolling = (isScrolling) => - useLocalesStore.setState({ isScrolling })