Skip to content

Commit 5f52c71

Browse files
Merge pull request #70 from inventree/frontend-trans-updates
Frontend translation support
2 parents db3ea73 + 74dfd41 commit 5f52c71

File tree

32 files changed

+463
-96
lines changed

32 files changed

+463
-96
lines changed

.github/workflows/build.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,15 @@ jobs:
6363
run: |
6464
cd output/MyCustomPlugin/frontend
6565
npm install
66+
npm run translate
6667
npm run build
67-
68+
test -f src/locales/de/messages.ts
69+
test -f src/locales/en/messages.ts
70+
- name: Check Compiled Output
71+
run: |
72+
cd output/MyCustomPlugin
73+
test -d my_custom_plugin/static
74+
test -d my_custom_plugin/static/assets
75+
test -f my_custom_plugin/static/Panel.js
76+
test -f my_custom_plugin/static/Dashboard.js
77+
test -f my_custom_plugin/static/.vite/manifest.json

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,7 @@ cython_debug/
171171
.pypirc
172172

173173
# Default generated plugin dir
174-
./MyCustomPlugin
174+
./MyCustomPlugin
175+
176+
# Windows files
177+
*Zone.Identifier

plugin_creator/frontend.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,12 @@ def frontend_features() -> dict:
1919

2020
def all_features() -> dict:
2121
"""Select all features by default."""
22-
return {
23-
key: True for key in frontend_features().keys()
24-
}
22+
return {key: True for key in frontend_features().keys()}
2523

2624

2725
def no_features() -> dict:
2826
"""Select no features by default."""
29-
return {
30-
key: False for key in frontend_features().keys()
31-
}
27+
return {key: False for key in frontend_features().keys()}
3228

3329

3430
def enable_translation() -> bool:
@@ -46,19 +42,19 @@ def select_features() -> dict:
4642
Choice(
4743
title=title,
4844
checked=True,
49-
) for title in frontend_features().values()
45+
)
46+
for title in frontend_features().values()
5047
]
5148

5249
selected = questionary.checkbox(
53-
"Select frontend features to enable",
54-
choices=choices
50+
"Select frontend features to enable", choices=choices
5551
).ask()
5652

57-
selected_keys = [key for key, value in frontend_features().items() if value in selected]
53+
selected_keys = [
54+
key for key, value in frontend_features().items() if value in selected
55+
]
5856

59-
return {
60-
key: key in selected_keys for key in frontend_features().keys()
61-
}
57+
return {key: key in selected_keys for key in frontend_features().keys()}
6258

6359

6460
def remove_frontend(plugin_dir: str) -> None:
@@ -71,20 +67,17 @@ def update_frontend(plugin_dir: str, context: dict) -> None:
7167

7268
info("Cleaning up frontend files...")
7369

74-
features = context['frontend']['features'] or []
75-
translation = context['frontend'].get('translation', False)
70+
features = context["frontend"]["features"] or []
71+
translation = context["frontend"].get("translation", False)
7672

7773
# Remove features which are not needed
7874
for feature in frontend_features().keys():
7975
if not features.get(feature, False):
8076
info(f"- Removing unused frontend feature: {feature}")
8177

82-
remove_file(
83-
plugin_dir,
84-
'frontend',
85-
'src',
86-
f'{feature.capitalize()}.tsx'
87-
)
78+
remove_file(plugin_dir, "frontend", "src", f"{feature.capitalize()}.tsx")
8879

8980
if not translation:
90-
remove_dir(plugin_dir, 'frontend', 'src', 'locales')
81+
remove_dir(plugin_dir, "frontend", "src", "locales")
82+
remove_file(plugin_dir, "frontend", "src", "locales.tsx")
83+
remove_file(plugin_dir, "frontend", ".linguirc")

plugin_creator/template/{{ cookiecutter.plugin_name }}/.github/workflows/ci.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ jobs:
3939
run: |
4040
cd frontend
4141
npm install
42+
{% if cookiecutter.frontend.translation -%}
43+
npm run translate
44+
{%- endif %}
4245
npm run build
4346
npm run lint
4447
{%- endif -%}

plugin_creator/template/{{ cookiecutter.plugin_name }}/.github/workflows/pypi.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ jobs:
3232
run: |
3333
cd frontend
3434
npm install
35+
{% if cookiecutter.frontend.translation -%}
36+
npm run translate
37+
{%- endif %}
3538
npm run build
3639
{%- endif %}
3740
- name: Build Binary

plugin_creator/template/{{ cookiecutter.plugin_name }}/.gitlab-ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@ frontend:
2727
- npm install
2828
script:
2929
- npm run build
30+
{% if cookiecutter.frontend.translation -%}
31+
- npm run translate
32+
{%- endif %}
3033
- npm run lint
3134
{%- endif -%}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"locales": [
3+
"de",
4+
"en",
5+
"es",
6+
"fr",
7+
"it",
8+
"ja",
9+
"ru",
10+
"zh_Hans",
11+
"zh_Hant",
12+
"pseudo-LOCALE"
13+
],
14+
"catalogs": [{
15+
"path": "src/locales/{locale}/messages",
16+
"include": ["src"],
17+
"exclude": ["**/node_modules/**", "./dist/**"]
18+
}],
19+
"format": "po",
20+
"orderBy": "origin",
21+
"sourceLocale": "en",
22+
"fallbackLocales": {
23+
"default": "en",
24+
"pseudo-LOCALE": "en"
25+
}
26+
}

plugin_creator/template/{{ cookiecutter.plugin_name }}/frontend/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"version": "{{ cookiecutter.plugin_version }}",
55
"type": "module",
66
"scripts": {
7+
{% if cookiecutter.frontend.translation -%}
8+
"extract": "lingui extract",
9+
"compile": "lingui compile --typescript",
10+
"translate": "lingui extract && lingui compile --typescript",
11+
{%- endif %}
712
"lint": "npx @biomejs/biome check ./src",
813
"lint:fix": "npx @biomejs/biome check ./src --fix",
914
"build": "tsc -b && vite build --emptyOutDir",
@@ -15,8 +20,13 @@
1520
"peerDependencies": {},
1621
"devDependencies": {
1722
"@biomejs/biome": "2.0.0",
23+
{% if cookiecutter.frontend.translation -%}
1824
"@lingui/cli": "latest",
1925
"@lingui/macro": "latest",
26+
"@lingui/swc-plugin": "latest",
27+
"@lingui/vite-plugin": "latest",
28+
"@vitejs/plugin-react-swc": "latest",
29+
{%- endif %}
2030
"@types/react": "latest",
2131
"@types/react-dom": "latest",
2232
"@vitejs/plugin-react": "^4.3.4",

plugin_creator/template/{{ cookiecutter.plugin_name }}/frontend/src/Panel.tsx

Lines changed: 74 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import { useCallback, useMemo, useState } from 'react';
2-
import { Alert, Button, Group, Stack, Text, Title } from '@mantine/core';
1+
import { useCallback, useEffect, useMemo, useState } from 'react';
2+
import { Alert, Button, Group, SimpleGrid, Stack, Text, Title } from '@mantine/core';
33
import { notifications } from '@mantine/notifications';
44
{% if "UrlsMixin" in cookiecutter.plugin_mixins.mixin_list -%}
55
import { useQuery } from '@tanstack/react-query';
66
{%- endif %}
77

8-
{% if cookiecutter.frontend.translation %}
9-
import { i18n } from '@lingui/core';
10-
import { Trans } from '@lingui/react';
11-
12-
// Translation support
13-
import { messages as deMessages } from './locales/de/messages.ts';
14-
import { messages as esMessages } from './locales/es/messages.ts';
15-
{% endif %}
8+
{% if cookiecutter.frontend.translation -%}
9+
import { t } from '@lingui/core/macro';
10+
import { LocalizedComponent } from './locale';
11+
{%- endif %}
1612

1713
// Import for type checking
1814
import { checkPluginVersion, type InvenTreePluginContext } from '@inventreedb/ui';
@@ -29,6 +25,14 @@ function {{ cookiecutter.plugin_name }}Panel({
2925
context: InvenTreePluginContext;
3026
}) {
3127

28+
// React hooks can be used within plugin components
29+
useEffect(() => {
30+
console.log("useEffect in plugin component:");
31+
console.log("- Model:", context.model);
32+
console.log("- ID:", context.id);
33+
}, [context.model, context.id]);
34+
35+
// Memoize the part ID as passed via the context object
3236
const partId = useMemo(() => {
3337
return context.model == ModelType.part ? context.id || null: null;
3438
}, [context.model, context.id]);
@@ -100,70 +104,76 @@ function {{ cookiecutter.plugin_name }}Panel({
100104
<Text>
101105
This is a custom panel for the {{ cookiecutter.plugin_name }} plugin.
102106
</Text>
103-
{% if cookiecutter.frontend.translation %}
104-
<Alert title='Translated Text' color='grape'>
105-
<Trans
106-
id='panel.greeting'
107-
message='Translated text, provided by custom code!'
108-
/>
109-
</Alert>
110-
{% endif %}
111-
<Group justify='apart' wrap='nowrap' gap='sm'>
112-
<Button color='blue' onClick={gotoDashboard}>
113-
Go to Dashboard
114-
</Button>
115-
{partId && <Button color='green' onClick={openForm}>
116-
Edit Part
117-
</Button>}
118-
<Button onClick={() => setCounter(counter + 1)}>
119-
Increment Counter
120-
</Button>
121-
<Text size='xl'>Counter: {counter}</Text>
122-
</Group>
123-
{instance ? (
124-
<Alert title="Instance Data" color="blue">
125-
{instance}
126-
</Alert>
127-
) : (
128-
<Alert title="No Instance" color="yellow">
129-
No instance data available
130-
</Alert>
131-
)}
132-
{% if "UrlsMixin" in cookiecutter.plugin_mixins.mixin_list -%}
133-
{apiQuery.isFetched && apiQuery.data && (
134-
<Alert color="green" title="API Query Data">
135-
{apiQuery.isFetching || apiQuery.isLoading ? (
136-
<Text>Loading...</Text>
137-
) : (
107+
<SimpleGrid cols={2}>
108+
{% if cookiecutter.frontend.translation -%}
109+
<Alert title='Translated Text' color='grape'>
138110
<Stack gap='xs'>
139-
<Text>Part Count: {apiQuery.data.part_count}</Text>
140-
<Text>Today: {apiQuery.data.today}</Text>
141-
<Text>Random Text: {apiQuery.data.random_text}</Text>
142-
<Button
143-
disabled={apiQuery.isFetching || apiQuery.isLoading}
144-
onClick={() => apiQuery.refetch()}>
145-
Reload Data
146-
</Button>
111+
<Text>{t`Translated text, provided by custom code!`}</Text>
112+
<Text>{t`Translations are loaded automatically.`}</Text>
113+
<Text>{t`Fallback locale is used if no translation is available`}</Text>
147114
</Stack>
115+
</Alert>
116+
{%- endif %}
117+
<Group justify='apart' wrap='nowrap' gap='sm'>
118+
<Button color='blue' onClick={gotoDashboard}>
119+
Go to Dashboard
120+
</Button>
121+
{partId && <Button color='green' onClick={openForm}>
122+
Edit Part
123+
</Button>}
124+
<Button onClick={() => setCounter(counter + 1)}>
125+
Increment Counter
126+
</Button>
127+
<Text size='xl'>Counter: {counter}</Text>
128+
</Group>
129+
{instance ? (
130+
<Alert title="Instance Data" color="blue">
131+
{instance}
132+
</Alert>
133+
) : (
134+
<Alert title="No Instance" color="yellow">
135+
No instance data available
136+
</Alert>
148137
)}
149-
</Alert>
150-
)}{%- endif %}
138+
{% if "UrlsMixin" in cookiecutter.plugin_mixins.mixin_list -%}
139+
{apiQuery.isFetched && apiQuery.data && (
140+
<Alert color="green" title="API Query Data">
141+
{apiQuery.isFetching || apiQuery.isLoading ? (
142+
<Text>Loading...</Text>
143+
) : (
144+
<Stack gap='xs'>
145+
<Text>Part Count: {apiQuery.data.part_count}</Text>
146+
<Text>Today: {apiQuery.data.today}</Text>
147+
<Text>Random Text: {apiQuery.data.random_text}</Text>
148+
<Button
149+
disabled={apiQuery.isFetching || apiQuery.isLoading}
150+
onClick={() => apiQuery.refetch()}>
151+
Reload Data
152+
</Button>
153+
</Stack>
154+
)}
155+
</Alert>
156+
)}{%- endif %}
157+
</SimpleGrid>
151158
</Stack>
152159
</>
153160
);
154161
}
155162

163+
156164
// This is the function which is called by InvenTree to render the actual panel component
157165
export function render{{ cookiecutter.plugin_name }}Panel(context: InvenTreePluginContext) {
158166
checkPluginVersion(context);
159167

160-
{% if cookiecutter.frontend.translation %}
161-
// Set up translations (if required)
162-
i18n.load({
163-
de: deMessages,
164-
es: esMessages
165-
});
166-
{% endif %}
167-
168-
return <{{ cookiecutter.plugin_name }}Panel context={context} />;
168+
{% if cookiecutter.frontend.translation -%}
169+
return (
170+
<LocalizedComponent locale={context.locale}>
171+
<{{ cookiecutter.plugin_name }}Panel context={context} />
172+
</LocalizedComponent>
173+
);
174+
{%- else -%}
175+
return (
176+
<{{ cookiecutter.plugin_name }}Panel context={context} />;
177+
);
178+
{%- endif %}
169179
}

0 commit comments

Comments
 (0)