Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/translations-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
upload_translations: false

download_translations: true
download_translations_args: '--language=it --language=es-ES --language=ko --language=zh-CN --language=ru --language=el --language=fr --language=uk --language=id --language=de --language=da --language=pt-PT --language=pl --language=ka'
download_translations_args: '--language=it --language=es-ES --language=ko --language=zh-CN --language=ru --language=el --language=fr --language=uk --language=id --language=de --language=da --language=pt-PT --language=pl --language=ka --language=ar'
localization_branch_name: update-translations-crowdin
push_translations: true
commit_message: 'Update translations'
Expand Down
16 changes: 10 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,14 @@
"resolutions": {},
"devDependencies": {
"@electron/rebuild": "^4.0.0",
"@eslint/js": "^9.0.0",
"@graphql-codegen/cli": "^6.0.0",
"@graphql-codegen/introspection": "^5.0.0",
"@graphql-codegen/typed-document-node": "^5.0.0",
"@graphql-codegen/typescript": "^5.0.0",
"@graphql-codegen/typescript-operations": "^5.0.0",
"@graphql-codegen/typed-document-node": "^5.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.0",
"@stylistic/eslint-plugin": "^4.0.0",
"@teamsupercell/typings-for-css-modules-loader": "^2.5.2",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
Expand All @@ -213,13 +215,11 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/rimraf": "^4.0.5",
"@types/stylis": "^4.2.7",
"@types/uuid": "^9.0.7",
"@types/webpack-bundle-analyzer": "^4.6.3",
"@types/webpack-env": "^1.18.4",
"@types/webpack-node-externals": "^3.0.4",
"@eslint/js": "^9.0.0",
"@stylistic/eslint-plugin": "^4.0.0",
"typescript-eslint": "^8.0.0",
"chalk": "^4.1.0",
"concurrently": "^9.0.0",
"core-js": "^3.35.0",
Expand All @@ -233,8 +233,8 @@
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.37.0",
"eslint-plugin-react-hooks": "^5.0.0",
"globals": "^16.0.0",
"file-loader": "^6.0.0",
"globals": "^16.0.0",
"html-webpack-plugin": "^5.6.0",
"husky": "^9.0.0",
"identity-obj-proxy": "^3.0.0",
Expand All @@ -252,6 +252,7 @@
"tsconfig-paths-webpack-plugin": "^4.1.0",
"tsx": "^4.21.0",
"typescript": "^5.6.2",
"typescript-eslint": "^8.0.0",
"url-loader": "^4.1.0",
"webpack": "^5.73.0",
"webpack-bundle-analyzer": "^5.0.0",
Expand All @@ -264,9 +265,11 @@
"@apollo/client": "^4.0.0",
"@apollo/server": "^5.0.0",
"@as-integrations/express5": "^1.0.0",
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
"@graphql-yoga/subscription": "^5.0.0",
"@mui/icons-material": "^7.0.0",
"@mui/material": "^7.0.0",
"@octokit/rest": "^22.0.0",
Expand All @@ -282,7 +285,6 @@
"get-port": "^5.1.1",
"graphql": "^16.7.1",
"graphql-scalars": "^1.23.0",
"@graphql-yoga/subscription": "^5.0.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^6.0.6",
"i18next": "^26.0.0",
Expand All @@ -304,6 +306,8 @@
"semver": "^7.3.8",
"simple-git": "^3.28.0",
"source-map-support": "^0.5.21",
"stylis": "^4.4.0",
"stylis-plugin-rtl": "^2.1.1",
"ts-node": "^10.9.2",
"type-graphql": "2.0.0-rc.3",
"typedi": "^0.10.0",
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ const loadPath = `${
searchParams.get('base_url') ?? 'http://localhost:3500/'
}/locales/{{lng}}/messages.json`;

// Keep the document direction and language in sync (e.g. RTL for Arabic)
i18n.on('languageChanged', (lng) => {
document.documentElement.dir = i18n.dir(lng);
document.documentElement.lang = lng;
});

i18n
.use(Backend)
.use(LanguageDetector)
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,9 @@
{
"value": "ka",
"label": "ქართული (Georgian)"
},
{
"value": "ar",
"label": "العربية (Arabic)"
}
]
365 changes: 365 additions & 0 deletions src/i18n/locales/ar/messages.json

Large diffs are not rendered by default.

33 changes: 28 additions & 5 deletions src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 're
import { Routes, Route, HashRouter, Navigate } from 'react-router';
import CssBaseline from '@mui/material/CssBaseline';
import '@fontsource/roboto';
import { ThemeProvider, StyledEngineProvider } from '@mui/material';
import { ThemeProvider } from '@mui/material';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { prefixer } from 'stylis';
import rtlPlugin from 'stylis-plugin-rtl';
import { useTranslation } from 'react-i18next';
import { ApolloProvider } from '@apollo/client/react';
import { createAppTheme } from './theme';
import { createAppTheme, ThemeDirection } from './theme';
import client from './gql';
import ConfiguratorView from './views/ConfiguratorView';
import LogsView from './views/LogsView';
Expand All @@ -23,13 +28,31 @@ import useBuildProgressNotifications from './hooks/useBuildProgressNotifications
import useBuildLogs from './hooks/useBuildLogs';
import useResolvedThemeMode from './hooks/useResolvedThemeMode';

// Emotion caches per text direction. The RTL cache flips left/right styles
// (margins, paddings, positions) for right-to-left languages such as Arabic.
// `prepend: true` replaces StyledEngineProvider's injectFirst.
const emotionCaches: Record<ThemeDirection, ReturnType<typeof createCache>> = {
ltr: createCache({ key: 'mui', prepend: true }),
rtl: createCache({
key: 'muirtl',
prepend: true,
stylisPlugins: [prefixer, rtlPlugin],
}),
};

interface ThemeWrapperProps {
children: React.ReactNode;
}

const ThemeWrapper: FunctionComponent<ThemeWrapperProps> = ({ children }) => {
const resolvedMode = useResolvedThemeMode();
const theme = useMemo(() => createAppTheme(resolvedMode), [resolvedMode]);
const { i18n } = useTranslation();
const language = i18n.resolvedLanguage ?? i18n.language;
const direction: ThemeDirection = i18n.dir(language);
const theme = useMemo(
() => createAppTheme(resolvedMode, direction, language),
[resolvedMode, direction, language],
);

// Sync the body/html background with the current theme.
// The flash-prevention script in index.ejs sets an inline background-color
Expand All @@ -42,12 +65,12 @@ const ThemeWrapper: FunctionComponent<ThemeWrapperProps> = ({ children }) => {
}, [theme]);

return (
<StyledEngineProvider injectFirst>
<CacheProvider value={emotionCaches[direction]}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</StyledEngineProvider>
</CacheProvider>
);
};

Expand Down
25 changes: 21 additions & 4 deletions src/ui/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import { createTheme, Theme } from '@mui/material';
import { arSD, Localization } from '@mui/material/locale';
import { darkPalette, lightPalette } from './palette';
import getComponentOverrides from './overrides';

export type ResolvedThemeMode = 'dark' | 'light';

export function createAppTheme(mode: ResolvedThemeMode): Theme {
export type ThemeDirection = 'ltr' | 'rtl';

// MUI built-in component strings (Alert close label, Autocomplete "No options", etc.)
const muiLocales: Record<string, Localization> = {
ar: arSD,
};

export function createAppTheme(
mode: ResolvedThemeMode,
direction: ThemeDirection = 'ltr',
language = 'en',
): Theme {
const palette = mode === 'dark' ? darkPalette : lightPalette;

// First pass: build theme with palette
const baseTheme = createTheme({
direction,
shape: {
borderRadius: 0,
},
palette,
});

// Second pass: build theme with component overrides that reference resolved tokens
return createTheme(baseTheme, {
components: getComponentOverrides(baseTheme),
});
return createTheme(
baseTheme,
{
components: getComponentOverrides(baseTheme),
},
muiLocales[language] ?? {},
);
}

export default createAppTheme('dark');
6 changes: 3 additions & 3 deletions src/ui/views/SettingsView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const SettingsView: FunctionComponent = () => {
icon={<LanguageIcon />}
title={t('SettingsView.LanguageSelector')}
/>
<CardContent style={{ paddingLeft: 26, marginTop: -18 }}>
<CardContent sx={{ paddingLeft: 3.25, marginTop: -2.25 }}>
<Omnibox
title={t('SettingsView.CurrentLanguage')}
currentValue={currentLanguage}
Expand All @@ -89,7 +89,7 @@ const SettingsView: FunctionComponent = () => {
icon={<Brightness6Icon />}
title={t('SettingsView.ThemeSelector')}
/>
<CardContent style={{ paddingLeft: 26, marginTop: -18 }}>
<CardContent sx={{ paddingLeft: 3.25, marginTop: -2.25 }}>
<FormControl>
<RadioGroup
value={appState.themeMode}
Expand Down Expand Up @@ -118,7 +118,7 @@ const SettingsView: FunctionComponent = () => {
icon={<DeveloperModeIcon />}
title={t('SettingsView.ApplicationOptions')}
/>
<CardContent style={{ paddingLeft: 26, marginTop: -18 }}>
<CardContent sx={{ paddingLeft: 3.25, marginTop: -2.25 }}>
<FormControlLabel
control={(
<Checkbox
Expand Down
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3190,6 +3190,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==

"@types/stylis@^4.2.7":
version "4.2.7"
resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.7.tgz#1813190525da9d2a2b6976583bdd4af5301d9fd4"
integrity sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==

"@types/tough-cookie@*":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304"
Expand Down Expand Up @@ -5008,6 +5013,11 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==

cssjanus@^2.0.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/cssjanus/-/cssjanus-2.3.1.tgz#66ba936aa5e2c57e869a2e14f69097a0a69fe835"
integrity sha512-gWZQ/S0tthU2KCc55C5zbjxQN0XPK1sT0qufCqwCNUauGIsPDOfdBMBWosP+Kd4wf2A2Nxm/z6JAP6yNGnS7QQ==

cssnano-preset-default@^7.0.12:
version "7.0.12"
resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-7.0.12.tgz#21e29fed4a8697952485995ffeeec20ff58945a4"
Expand Down Expand Up @@ -10922,11 +10932,23 @@ stylehacks@^7.0.5:
browserslist "^4.28.1"
postcss-selector-parser "^7.1.1"

stylis-plugin-rtl@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/stylis-plugin-rtl/-/stylis-plugin-rtl-2.1.1.tgz#16707809c878494835f77e5d4aadaae3db639b5e"
integrity sha512-q6xIkri6fBufIO/sV55md2CbgS5c6gg9EhSVATtHHCdOnbN/jcI0u3lYhNVeuI65c4lQPo67g8xmq5jrREvzlg==
dependencies:
cssjanus "^2.0.1"

stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==

stylis@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.4.0.tgz#c5846c9345f4bfc51bd0cbd7ca35a0744f485a5d"
integrity sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==

sumchecker@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
Expand Down
Loading