Skip to content

Commit 7a3afb4

Browse files
authored
Merge pull request #334 from debrief/331-i18n
331 i18n
2 parents 18f3398 + 807f9d1 commit 7a3afb4

32 files changed

Lines changed: 1294 additions & 217 deletions

File tree

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@
4242
"dom-to-image": "^2.6.0",
4343
"electron-window-state": "^5.0.3",
4444
"font-gis": "^1.0.6",
45+
"i18next": "^24.2.3",
46+
"i18next-browser-languagedetector": "^8.0.4",
4547
"leaflet": "^1.9.4",
4648
"leaflet.nauticscale": "^1.1.0",
4749
"leaflet.polylinemeasure": "^3.0.0",
4850
"lodash": "^4.17.21",
4951
"react": "^19.0.0",
5052
"react-dom": "^19.0.0",
5153
"react-error-boundary": "^5.0.0",
54+
"react-i18next": "^15.4.1",
5255
"react-leaflet": "^5.0.0",
5356
"react-leaflet-custom-control": "^1.4.0",
5457
"react-leaflet-geoman-v2": "^1.0.1",

src/components/BackdropForm/index.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Feature, Geometry } from 'geojson'
22
import { Checkbox, Form, Input } from 'antd'
33
import { useEffect, useState } from 'react'
4+
import { useTranslation } from 'react-i18next'
45
import { BackdropProps } from '../../types'
56
import './index.css'
67

@@ -12,6 +13,7 @@ export interface BackdropFormProps {
1213

1314

1415
export const BackdropForm: React.FC<BackdropFormProps> = ({backdrop, onChange, create = false}) => {
16+
const { t } = useTranslation()
1517
const [state, setState] = useState<BackdropProps | null>(null)
1618
const [form] = Form.useForm()
1719

@@ -49,39 +51,39 @@ export const BackdropForm: React.FC<BackdropFormProps> = ({backdrop, onChange, c
4951
onValuesChange={localChange}
5052
size='small'>
5153
<Form.Item<BackdropProps>
52-
label='Name'
54+
label={t('forms.common.name')}
5355
name='name'
5456
style={itemStyle}
55-
rules={[{ required: true, message: 'Please enter backdrop name!' }]}>
57+
rules={[{ required: true, message: t('forms.common.nameRequired') }]}>
5658
<Input/>
5759
</Form.Item>
5860
<Form.Item<BackdropProps>
59-
label='Visible'
61+
label={t('forms.common.visible')}
6062
name={'visible'}
6163
style={itemStyle}
6264
valuePropName="checked" >
6365
<Checkbox />
6466
</Form.Item>
6567

6668
{ create && <><Form.Item<BackdropProps>
67-
label='URL'
69+
label={t('forms.common.url')}
6870
name='url'
6971
style={itemStyle}
70-
rules={[{ required: true, message: 'Please enter backdrop URL!' }]}>
72+
rules={[{ required: true, message: t('forms.common.urlRequired') }]}>
7173
<Input.TextArea rows={3}/>
7274
</Form.Item>
7375
<Form.Item<BackdropProps>
74-
label='Max Native Zoom'
76+
label={t('forms.common.maxNativeZoom')}
7577
name= 'maxNativeZoom'
7678
style={itemStyle}
77-
rules={[{ required: true, message: 'Please enter backdrop max native zoom!' }]}>
79+
rules={[{ required: true, message: t('forms.common.maxNativeZoomRequired') }]}>
7880
<Input/>
7981
</Form.Item>
8082
<Form.Item<BackdropProps>
81-
label='Max Zoom'
83+
label={t('forms.common.maxZoom')}
8284
name='maxZoom'
8385
style={itemStyle}
84-
rules={[{ required: true, message: 'Please enter backdrop max zoom!' }]}>
86+
rules={[{ required: true, message: t('forms.common.maxZoomRequired') }]}>
8587
<Input/>
8688
</Form.Item>
8789
</>}

src/components/BuoyFieldForm/index.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from 'antd'
99
import { Color } from 'antd/es/color-picker'
1010
import { useEffect, useState } from 'react'
11+
import { useTranslation } from 'react-i18next'
1112
import { BuoyFieldProps } from '../../types'
1213
import { presetColors } from '../../helpers/standardShades'
1314
import './index.css'
@@ -29,6 +30,7 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
2930
field,
3031
onChange,
3132
}) => {
33+
const { t } = useTranslation()
3234
const [state, setState] = useState<BuoyFieldProps | null>(null)
3335

3436
useEffect(() => {
@@ -96,34 +98,34 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
9698
size='small'
9799
>
98100
<Form.Item<FormTypeProps>
99-
label='Name'
101+
label={t('forms.common.name')}
100102
name='name'
101103
style={itemStyle}
102-
rules={[{ required: true, message: 'Please enter track name!' }]}
104+
rules={[{ required: true, message: t('forms.common.nameRequired') }]}
103105
>
104106
<Input />
105107
</Form.Item>
106108
<Form.Item<FormTypeProps>
107-
label='Short name'
109+
label={t('forms.common.shortName')}
108110
name='shortName'
109111
style={itemStyle}
110-
rules={[{ required: true, message: 'Please enter short name!' }]}
112+
rules={[{ required: true, message: t('forms.common.shortNameRequired') }]}
111113
>
112114
<Input />
113115
</Form.Item>
114116
<Form.Item<FormTypeProps>
115-
label='Visible'
117+
label={t('forms.common.visible')}
116118
name={'visible'}
117119
style={itemStyle}
118120
valuePropName='checked'
119121
>
120122
<Checkbox />
121123
</Form.Item>
122124
<Form.Item<FormTypeProps>
123-
label='Color'
125+
label={t('forms.common.color')}
124126
name='marker-color'
125127
style={itemStyle}
126-
rules={[{ required: true, message: 'color is required!' }]}
128+
rules={[{ required: true, message: t('forms.common.colorRequired') }]}
127129
>
128130
<ColorPicker
129131
style={{ marginLeft: 0 }}
@@ -132,11 +134,11 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
132134
presets={presetColors}
133135
/>
134136
</Form.Item>
135-
<Form.Item<FormTypeProps> label='Time' style={itemStyle} name='dTime'>
137+
<Form.Item<FormTypeProps> label={t('forms.common.time')} style={itemStyle} name='dTime'>
136138
<DatePicker showTime format={'MMM DDHHmm'} />
137139
</Form.Item>
138140
<Form.Item<FormTypeProps>
139-
label='Time end'
141+
label={t('forms.common.timeEnd')}
140142
style={itemStyle}
141143
// validate that dTimeEnd is after dTime
142144
rules={[
@@ -145,7 +147,7 @@ export const BuoyFieldForm: React.FC<FieldFormProps> = ({
145147
// if there is a value, check if it's after the dTime
146148
return !value || getFieldValue('dTime') < value
147149
? Promise.resolve()
148-
: Promise.reject(new Error('Time-end must be after time!'))
150+
: Promise.reject(new Error(t('forms.common.timeEndAfterTime')))
149151
},
150152
}),
151153
]}

src/components/ControlPanel/TimeControls.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { TimeSupport } from '../../helpers/time-support'
1010
import { formatInTimeZone } from 'date-fns-tz'
1111
import { useDocContext } from '../../state/DocContext'
12+
import { useTranslation } from 'react-i18next'
1213

1314
interface TimeControlsProps {
1415
bounds: [number, number] | null
@@ -66,6 +67,7 @@ const TimeButton: React.FC<TimeButtonProps> = ({
6667
}
6768

6869
const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
70+
const { t } = useTranslation()
6971
const { time, setTime, interval, setInterval } = useDocContext()
7072
const [stepTxt, setStepTxt] = useState<string>('01h00m')
7173

@@ -140,9 +142,9 @@ const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
140142
>
141143
<thead>
142144
<tr>
143-
<th>Start</th>
144-
<th>Step</th>
145-
<th>End</th>
145+
<th>{t('controlPanel.start')}</th>
146+
<th>{t('controlPanel.step')}</th>
147+
<th>{t('controlPanel.end')}</th>
146148
</tr>
147149
</thead>
148150
<tbody>
@@ -164,15 +166,15 @@ const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
164166
<tr style={{ fontFamily: 'monospace' }}>
165167
<td>
166168
<TimeButton
167-
tooltip='Jump to start'
169+
tooltip={t('controlPanel.jumpToStart')}
168170
icon={<FastBackwardOutlined style={largeIcon} />}
169171
forward={false}
170172
large={true}
171173
doStep={doStep}
172174
disabled={!time.filterApplied}
173175
/>
174176
<TimeButton
175-
tooltip='Step backward'
177+
tooltip={t('controlPanel.stepBackward')}
176178
icon={<StepBackwardOutlined style={largeIcon} />}
177179
forward={false}
178180
large={false}
@@ -183,15 +185,15 @@ const TimeControls: FC<TimeControlsProps> = ({ bounds }) => {
183185
<td></td>
184186
<td>
185187
<TimeButton
186-
tooltip='Step forward'
188+
tooltip={t('controlPanel.stepForward')}
187189
icon={<StepForwardOutlined style={largeIcon} />}
188190
forward={true}
189191
large={false}
190192
doStep={doStep}
191193
disabled={!time.filterApplied}
192194
/>
193195
<TimeButton
194-
tooltip='Jump to end'
196+
tooltip={t('controlPanel.jumpToEnd')}
195197
icon={<FastForwardOutlined style={largeIcon} />}
196198
forward={true}
197199
large={true}

src/components/ControlPanel/index.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import React, { useMemo, useState } from 'react'
1313
import { useAppSelector } from '../../state/hooks'
1414
import { UndoModal } from '../UndoModal'
1515
import { useDocContext } from '../../state/DocContext'
16+
import { useTranslation } from 'react-i18next'
1617

1718

1819
import { SampleDataLoader } from '../SampleDataLoader'
@@ -27,32 +28,33 @@ export interface TimeProps {
2728

2829

2930
const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
31+
const { t } = useTranslation()
3032
const canUndo = useAppSelector(state => state.fColl.past.length > 1)
3133
const canRedo = useAppSelector(state => state.fColl.future.length > 0)
3234
const { viewportFrozen, setViewportFrozen, copyMapToClipboard, time, setTime } = useDocContext()
3335
const [undoModalVisible, setUndoModalVisible] = useState(false)
3436

3537
const undoRedoTitle = useMemo(() => {
3638
if(canUndo && canRedo) {
37-
return 'Undo/Redo ...'
39+
return t('controlPanel.undoRedo')
3840
} else if (canUndo) {
39-
return 'Undo ...'
41+
return t('controlPanel.undo')
4042
} else if (canRedo) {
41-
return 'Redo ...'
43+
return t('controlPanel.redo')
4244
} else {
4345
return null
4446
}
45-
}, [canUndo, canRedo])
47+
}, [canUndo, canRedo, t])
4648

4749
const toggleFreezeViewport = () => {
4850
setViewportFrozen(!viewportFrozen)
4951
}
5052

5153
const copyTooltip = useMemo(() => {
5254
return viewportFrozen
53-
? 'Copy snapshot of map to the clipboard'
54-
: 'Lock the viewport in order to take a snapshot of the map'
55-
}, [viewportFrozen])
55+
? t('controlPanel.copySnapshot')
56+
: t('controlPanel.lockViewportFirst')
57+
}, [viewportFrozen, t])
5658

5759
const toggleFilterApplied = () => {
5860
setTime(prevTime => ({ ...prevTime, filterApplied: !prevTime.filterApplied }))
@@ -61,18 +63,18 @@ const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
6163
const buttonStyle = { margin: '0 5px' }
6264

6365
const saveButton = useMemo(() => {
64-
return <Tooltip placement='bottom' title={isDirty ? 'Save changes' : 'Document unchanged'}>
66+
return <Tooltip placement='bottom' title={isDirty ? t('controlPanel.saveChanges') : t('controlPanel.documentUnchanged')}>
6567
<Button onClick={handleSave} disabled={!isDirty} variant='outlined' >
6668
<SaveOutlined/>
6769
</Button>
6870
</Tooltip>
69-
}, [handleSave, isDirty])
71+
}, [handleSave, isDirty, t])
7072

7173
const enableTip = useMemo(() => {
7274
return bounds
73-
? 'Enable time controls, to filter tracks by time'
74-
: 'No time data available'
75-
}, [bounds])
75+
? t('controlPanel.enableTimeControls')
76+
: t('controlPanel.noTimeData')
77+
}, [bounds, t])
7678

7779
return (
7880
<>
@@ -82,7 +84,7 @@ const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
8284
<Col span={20} style={{ textAlign: 'left' , display: 'flex', alignItems: 'center'}}>
8385
<Tooltip
8486
mouseEnterDelay={0.8}
85-
title='Lock viewport to prevent accidental map movement. When time filtering, mouse wheel updates time'
87+
title={t('controlPanel.lockViewport')}
8688
>
8789
<Button
8890
style={buttonStyle}
@@ -107,7 +109,7 @@ const ControlPanel: React.FC<TimeProps> = ({ bounds, handleSave, isDirty }) => {
107109
{time.filterApplied ? <FilterFilled /> : <FilterOutlined />}
108110
</Button>
109111
</Tooltip>
110-
<Tooltip placement='bottom' title={undoRedoTitle || 'Nothing to undo/redo'}>
112+
<Tooltip placement='bottom' title={undoRedoTitle || t('controlPanel.nothingToUndoRedo')}>
111113
<Button
112114
style={buttonStyle}
113115
className='undo-redo-button'

src/components/Document/index.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Alert, Card, ConfigProvider, Modal, Splitter, Tabs } from 'antd'
22
import { useCallback, useEffect, useRef, useState, useMemo } from 'react'
33
import { Feature, Geometry, GeoJsonProperties } from 'geojson'
4+
import { useTranslation } from 'react-i18next'
45
import { useAppDispatch, useAppSelector } from '../../state/hooks'
56
import { useDocContext } from '../../state/DocContext'
67
import { useAppContext } from '../../state/AppContext'
@@ -51,6 +52,7 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
5152
const features = useAppSelector(selectFeatures)
5253
const documentContents = useAppSelector(state => state.fColl.present.data)
5354
const dispatch = useAppDispatch()
55+
const { t } = useTranslation()
5456
const { setTime, time, message, setMessage, interval } = useDocContext()
5557
const [timeBounds, setTimeBounds] = useState<[number, number] | null>(null)
5658
const [graphOpen, setGraphOpen] = useState(false)
@@ -148,7 +150,7 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
148150
handler.handle(await file.text(), features, dispatch)
149151
} catch (e) {
150152
console.error('handler error', file, handler, e)
151-
setMessage({ title: 'Error', severity: 'error', message: 'Handling error: ' + e })
153+
setMessage({ title: t('documents.error'), severity: 'error', message: t('documents.handlingError') + e })
152154
}
153155
}
154156
}
@@ -185,18 +187,18 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
185187
const doc = JSON.stringify(documentContents)
186188
await window.electron.saveFile(filePath, doc)
187189
} else {
188-
window.alert('Local save not supportedin browser')
190+
window.alert(t('documents.localSaveNotSupported'))
189191
}
190-
}, [filePath, documentContents])
192+
}, [filePath, documentContents, t])
191193

192194
const detailTabs = [ {
193195
key: '1',
194-
label: 'Detail',
196+
label: t('document.detail'),
195197
children: <Properties />
196198
},
197199
{
198200
key: '2',
199-
label: 'Graphs',
201+
label: t('document.graphs'),
200202
children: <GraphsPanel width={splitterWidths ? splitterWidths[0] : 300} height={splitterHeights ? splitterHeights[2] : 400} />
201203
}]
202204

@@ -240,12 +242,12 @@ function Document({ filePath, withSampleData }: { filePath?: string, withSampleD
240242
<Splitter.Panel key='left' collapsible defaultSize='300' min='200' max='600'>
241243
<Splitter layout="vertical" style={{ height: '100%', boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)' }} onResizeEnd={handleSplitterVerticalResize}>
242244
<Splitter.Panel style={{minHeight: '170px'}} defaultSize='170' min='170' max='170' resizable={false}>
243-
<Card title='Control Panel'>
245+
<Card title={t('document.controlPanel')}>
244246
<ControlPanel isDirty={dirty} handleSave={doSave} bounds={timeBounds}/>
245247
</Card>
246248
</Splitter.Panel>
247249
<Splitter.Panel style={{overflow: 'visible'}} >
248-
<Card title='Layers' style={{width: '100%', height: '100%'}}>
250+
<Card title={t('document.layers')} style={{width: '100%', height: '100%'}}>
249251
{features && <Layers splitterWidths={splitterHeights ? splitterHeights[1] : 330} openGraph={() => setGraphOpen(true)} />}
250252
</Card>
251253
</Splitter.Panel>

0 commit comments

Comments
 (0)