Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce i18n framework #3036

Merged
merged 7 commits into from
Aug 11, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"build:browser": "./bin/package-browser",
"build:desktop": "./bin/package-electron",
"build:api": "yarn workspace @actual-app/api build",
"generate:i18n": "yarn workspace @actual-app/web generate:i18n",
"test": "yarn workspaces foreach --all --parallel --verbose run test",
"test:debug": "yarn workspaces foreach --all --verbose run test",
"e2e": "yarn workspaces foreach --all --parallel --verbose run e2e",
Expand Down
14 changes: 14 additions & 0 deletions packages/desktop-client/i18next-parser.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
input: ['src/**/*.{js,jsx,ts,tsx}', '../loot-core/src/**/*.{js,jsx,ts,tsx}'],
output: 'src/locale/$LOCALE.json',
locales: ['en'],
sort: true,
keySeparator: false,
namespaceSeparator: false,
defaultValue: (locale, ns, key, value) => {
if (locale === 'en') {
return value || key;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to pre-fill the base en language file with exactly the same content as in the source.

}
return '';
},
};
5 changes: 5 additions & 0 deletions packages/desktop-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
"downshift": "7.6.2",
"focus-visible": "^4.1.5",
"glamor": "^2.20.40",
"i18next": "^23.11.5",
"i18next-parser": "^9.0.0",
"i18next-resources-to-backend": "^1.2.1",
"inter-ui": "^3.19.3",
"jest": "^27.5.1",
"jest-watch-typeahead": "^2.2.2",
Expand All @@ -55,6 +58,7 @@
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.12",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.1.2",
"react-markdown": "^8.0.7",
"react-modal": "3.16.1",
"react-redux": "7.2.9",
Expand Down Expand Up @@ -86,6 +90,7 @@
"build": "vite build",
"build:browser": "cross-env ./bin/build-browser",
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",
"generate:i18n": "i18next",
"test": "vitest",
"e2e": "npx playwright test --browser=chromium",
"vrt": "cross-env VRT=true npx playwright test --browser=chromium"
Expand Down
14 changes: 9 additions & 5 deletions packages/desktop-client/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type FallbackProps,
} from 'react-error-boundary';
import { HotkeysProvider } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';

import {
Expand Down Expand Up @@ -41,6 +42,7 @@ type AppInnerProps = {
};

function AppInner({ budgetId, cloudFileId }: AppInnerProps) {
const { t } = useTranslation();
const [initializing, setInitializing] = useState(true);
const { showBoundary: showErrorBoundary } = useErrorBoundary();
const loadingText = useSelector((state: State) => state.app.loadingText);
Expand All @@ -51,34 +53,36 @@ function AppInner({ budgetId, cloudFileId }: AppInnerProps) {

dispatch(
setAppState({
loadingText: 'Initializing the connection to the local database...',
loadingText: t('Initializing the connection to the local database...'),
}),
);
await initConnection(socketName);

// Load any global prefs
dispatch(
setAppState({
loadingText: 'Loading global preferences...',
loadingText: t('Loading global preferences...'),
}),
);
await dispatch(loadGlobalPrefs());

// Open the last opened budget, if any
dispatch(
setAppState({
loadingText: 'Opening last budget...',
loadingText: t('Opening last budget...'),
}),
);
const budgetId = await send('get-last-opened-backup');
if (budgetId) {
await dispatch(loadBudget(budgetId, 'Loading the last budget file...'));
await dispatch(
loadBudget(budgetId, t('Loading the last budget file...')),
);

// Check to see if this file has been remotely deleted (but
// don't block on this in case they are offline or something)
dispatch(
setAppState({
loadingText: 'Retrieving remote files...',
loadingText: t('Retrieving remote files...'),
}),
);
send('get-remote-files').then(files => {
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop-client/src/components/common/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function Keybinding({ keyName }: KeybindingProps) {
);
}

type MenuItem = {
export type MenuItem = {
type?: string | symbol;
name: string;
disabled?: boolean;
Expand Down
34 changes: 20 additions & 14 deletions packages/desktop-client/src/components/manager/ConfigServer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-strict-ignore
import React, { useState, useEffect } from 'react';
import { Trans, useTranslation } from 'react-i18next';

import {
isNonProductionEnvironment,
Expand All @@ -20,6 +21,7 @@ import { Title } from './subscribe/common';

export function ConfigServer() {
useSetThemeColor(theme.mobileConfigServerViewTheme);
const { t } = useTranslation();
const { createBudget, signOut, loggedIn } = useActions();
const navigate = useNavigate();
const [url, setUrl] = useState('');
Expand All @@ -34,9 +36,13 @@ export function ConfigServer() {
function getErrorMessage(error: string) {
switch (error) {
case 'network-failure':
return 'Server is not running at this URL. Make sure you have HTTPS set up properly.';
return t(
'Server is not running at this URL. Make sure you have HTTPS set up properly.',
);
default:
return 'Server does not look like an Actual server. Is it set up correctly?';
return t(
'Server does not look like an Actual server. Is it set up correctly?',
);
}
}

Expand Down Expand Up @@ -91,7 +97,7 @@ export function ConfigServer() {

return (
<View style={{ maxWidth: 500, marginTop: -30 }}>
<Title text="Where’s the server?" />
<Title text={t('Where’s the server?')} />

<Text
style={{
Expand All @@ -101,16 +107,16 @@ export function ConfigServer() {
}}
>
{currentUrl ? (
<>
<Trans>
Existing sessions will be logged out and you will log in to this
server. We will validate that Actual is running at this URL.
</>
</Trans>
) : (
<>
<Trans>
There is no server configured. After running the server, specify the
URL here to use the app. You can always change this later. We will
validate that Actual is running at this URL.
</>
</Trans>
)}
</Text>

Expand All @@ -130,7 +136,7 @@ export function ConfigServer() {
<View style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}>
<BigInput
autoFocus={true}
placeholder="https://example.com"
placeholder={t('https://example.com')}
value={url || ''}
onChangeValue={setUrl}
style={{ flex: 1, marginRight: 10 }}
Expand All @@ -142,15 +148,15 @@ export function ConfigServer() {
style={{ fontSize: 15 }}
onPress={onSubmit}
>
OK
{t('OK')}
</ButtonWithLoading>
{currentUrl && (
<Button
variant="bare"
style={{ fontSize: 15, marginLeft: 10 }}
onPress={() => navigate(-1)}
>
Cancel
{t('Cancel')}
</Button>
)}
</View>
Expand All @@ -169,7 +175,7 @@ export function ConfigServer() {
style={{ color: theme.pageTextLight }}
onPress={onSkip}
>
Stop using a server
{t('Stop using a server')}
</Button>
) : (
<>
Expand All @@ -183,15 +189,15 @@ export function ConfigServer() {
}}
onPress={onSameDomain}
>
Use current domain
{t('Use current domain')}
</Button>
)}
<Button
variant="bare"
style={{ color: theme.pageTextLight, margin: 5 }}
onPress={onSkip}
>
Don’t use a server
{t('Don’t use a server')}
</Button>

{isNonProductionEnvironment() && (
Expand All @@ -200,7 +206,7 @@ export function ConfigServer() {
style={{ marginLeft: 15 }}
onPress={onCreateTestFile}
>
Create test file
{t('Create test file')}
</Button>
)}
</>
Expand Down
75 changes: 45 additions & 30 deletions packages/desktop-client/src/components/manager/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';

import { useActions } from '../../hooks/useActions';
import { styles, theme } from '../../style';
Expand All @@ -9,6 +10,7 @@ import { Text } from '../common/Text';
import { View } from '../common/View';

export function WelcomeScreen() {
const { t } = useTranslation();
const { createBudget, pushModal } = useActions();

return (
Expand All @@ -21,39 +23,50 @@ export function WelcomeScreen() {
marginBlock: 20,
}}
>
<Text style={styles.veryLargeText}>Let’s get started!</Text>
<Text style={styles.veryLargeText}>{t('Let’s get started!')}</Text>
<View style={{ overflowY: 'auto' }}>
<Paragraph>
Actual is a personal finance tool that focuses on beautiful design and
a slick user experience.{' '}
<strong>Editing your data should be as fast as possible.</strong> On
top of that, we want to provide powerful tools to allow you to do
whatever you want with your data.
<Trans>
Actual is a personal finance tool that focuses on beautiful design
and a slick user experience.{' '}
<strong>Editing your data should be as fast as possible.</strong> On
top of that, we want to provide powerful tools to allow you to do
whatever you want with your data.
</Trans>
</Paragraph>
<Paragraph>
Currently, Actual implements budgeting based on a{' '}
<Link
variant="external"
to="https://actualbudget.org/docs/budgeting/"
linkColor="purple"
>
monthly envelope system
</Link>
. Consider taking our{' '}
<Link
variant="external"
to="https://actualbudget.org/docs/tour/"
linkColor="purple"
>
guided tour
</Link>{' '}
to help you get your bearings, and check out the rest of the
documentation while you’re there to learn more about advanced topics.
<Trans>
Currently, Actual implements budgeting based on a{' '}
<Link
variant="external"
to="https://actualbudget.org/docs/budgeting/"
linkColor="purple"
>
monthly envelope system
</Link>
.
</Trans>{' '}
<Trans>
Consider taking our{' '}
<Link
variant="external"
to="https://actualbudget.org/docs/tour/"
linkColor="purple"
>
guided tour
</Link>{' '}
to help you get your bearings, and check out the rest of the
documentation while you’re there to learn more about advanced
topics.
</Trans>
</Paragraph>
<Paragraph style={{ color: theme.pageTextLight }}>
Get started by importing an existing budget file from Actual or
another budgeting app, create a demo budget file, or start fresh with
an empty budget. You can always create or import another budget later.
<Trans>
Get started by importing an existing budget file from Actual or
another budgeting app, create a demo budget file, or start fresh
with an empty budget. You can always create or import another budget
later.
</Trans>
</Paragraph>
</View>
<View
Expand All @@ -64,7 +77,9 @@ export function WelcomeScreen() {
flexShrink: 0,
}}
>
<Button onPress={() => pushModal('import')}>Import my budget</Button>
<Button onPress={() => pushModal('import')}>
{t('Import my budget')}
</Button>
<View
style={{
flexDirection: 'row',
Expand All @@ -73,10 +88,10 @@ export function WelcomeScreen() {
}}
>
<Button onPress={() => createBudget({ testMode: true })}>
View demo
{t('View demo')}
</Button>
<Button variant="primary" onPress={() => createBudget()}>
Start fresh
{t('Start fresh')}
</Button>
</View>
</View>
Expand Down
Loading
Loading