More languages
Seto Elkahfi committed
Jan 23, 2022 at 16:08 UTC
5c2e1d99f373867bbbdb197b3fb3f0e23fb0e3c6
16 files changed
+282
-63
src/components/App.tsx
+21
-6
@@ -5,14 +5,22 @@ import FooterLinks from './FooterLinks';
5
import { IntlProvider, addLocaleData } from 'react-intl';
6
import { BrowserRouter } from 'react-router-dom';
7
import Firebase, { FirebaseContext } from '../components/Firebase';
8
-import locale_en from 'react-intl/locale-data/en';
9
-import locale_se from 'react-intl/locale-data/se';
10
-import locale_id from 'react-intl/locale-data/id';
11
-import messages_se from "../translations/se.json";
8
+import localeEn from 'react-intl/locale-data/en';
9
+import localeSe from 'react-intl/locale-data/se';
10
+import localeId from 'react-intl/locale-data/id';
11
+import localeDe from 'react-intl/locale-data/de';
12
+import localeFr from 'react-intl/locale-data/fr';
13
+import localeZh from 'react-intl/locale-data/zh';
14
+import localeEs from 'react-intl/locale-data/es';
15
+import messages_sv from "../translations/sv.json";
16
import messages_id from "../translations/id.json";
17
import messages_en from "../translations/en.json";
18
+import messagesDe from "../translations/de.json";
19
+import messagesFr from "../translations/fr.json";
20
+import messagesZh from "../translations/zh.json";
21
+import messagesEs from "../translations/es.json";
22
15
-addLocaleData([...locale_en, ...locale_id, ...locale_se]);
23
+addLocaleData([...localeEn, ...localeId, ...localeSe, ...localeDe, ...localeFr, ...localeZh, ...localeEs]);
24
25
let i18nConfig = {
26
language: 'en',
@@ -31,13 +39,20 @@ class App extends Component<AppProps, AppState> {
39
40
onChangeLanguage(language: string) {
41
switch (language) {
34
- case 'se': i18nConfig.messages = messages_se; break;
42
+ case 'se': i18nConfig.messages = messages_sv; break;
43
case 'en': i18nConfig.messages = messages_en; break;
44
case 'id': i18nConfig.messages = messages_id; break;
45
+ case 'de': i18nConfig.messages = messagesDe; break;
46
+ case 'fr': i18nConfig.messages = messagesFr; break;
47
+ case 'zh': i18nConfig.messages = messagesZh; break;
48
+ case 'es': i18nConfig.messages = messagesEs; break;
49
default: i18nConfig.messages = messages_en; break;
50
}
51
i18nConfig.language = language;
52
this.setState({ language: language })
53
+ if (this.firebase) {
54
+ this.firebase.setLanguage(language)
55
+ }
56
}
57
58
createFirebaseContextIfNeeded(language: string): Firebase {
src/components/AudioPlayer.tsx
+82
-30
@@ -3,13 +3,13 @@
3
import axios from 'axios';
4
import React, { CSSProperties } from 'react';
5
import { Component } from 'react';
6
-import { Col, Row, Spinner, ToggleButtonGroup } from 'react-bootstrap';
6
+import { Col, Row, Spinner } from 'react-bootstrap';
7
import YouTube, { Options } from 'react-youtube';
8
import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys, GiPlayButton, GiPauseButton } from 'react-icons/gi';
9
import { IconContext } from 'react-icons';
10
import { db, AudioFiles } from './db';
11
-import { Channel, Player, Time, Transport } from 'tone';
12
-import { threadId } from 'worker_threads';
11
+import { FormattedMessage } from 'react-intl';
12
+import { Channel, Player} from 'tone';
13
14
const buttonStyle: CSSProperties = {
15
@@ -25,6 +25,12 @@ interface AudioFile {
25
results: Result[]
26
}
27
28
+interface YouTubeEventTarget {
29
+ pauseVideo: () => void,
30
+ playVideo: () => void,
31
+ seekTo: (arg0: number) => void
32
+}
33
+
34
type AudioProps = {
35
audioFileId: string
36
}
@@ -54,6 +60,8 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
60
drumsPlayer: Player | null = null;
61
pianoPlayer: Player | null = null;
62
63
+ youTubeEventTarget: YouTubeEventTarget | null = null
64
+
65
constructor(props: AudioProps) {
66
super(props);
67
this.state = {
@@ -61,7 +69,7 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
69
playerTime: 0,
70
isPlayerReady: false,
71
isYoutubePlayerReady: false,
64
- isPlaying: true,
72
+ isPlaying: false,
73
vocalAudioOn: true,
74
guitarAudioOn: true,
75
bassAudioOn: true,
@@ -82,7 +90,12 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
90
}
91
92
componentWillUnmount() {
85
- this.togglePlayAudio();
93
+ this.setState({isPlaying: false})
94
+ this.vocalPlayer?.stop()
95
+ this.bassPlayer?.stop()
96
+ this.drumsPlayer?.stop()
97
+ this.pianoPlayer?.stop()
98
+ this.otherPlayer?.stop()
99
}
100
101
downloadAudioFilesIfNeeded() {
@@ -195,17 +208,20 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
208
togglePlayAudio() {
209
this.setState({isPlaying: !this.state.isPlaying})
210
if (this.state.isPlaying) {
198
- this.vocalPlayer?.start()
199
- this.bassPlayer?.start()
200
- this.drumsPlayer?.start()
201
- this.pianoPlayer?.start()
202
- this.otherPlayer?.start()
211
+ this.vocalPlayer?.stop()
212
+ this.bassPlayer?.stop()
213
+ this.drumsPlayer?.stop()
214
+ this.pianoPlayer?.stop()
215
+ this.otherPlayer?.stop()
216
+ this.youTubeEventTarget?.pauseVideo()
217
} else {
204
- this.vocalPlayer?.stop();
205
- this.bassPlayer?.stop();
206
- this.drumsPlayer?.stop();
207
- this.pianoPlayer?.stop();
208
- this.otherPlayer?.stop();
218
+ this.youTubeEventTarget?.seekTo(0)
219
+ this.youTubeEventTarget?.playVideo()
220
+ this.vocalPlayer?.start();
221
+ this.bassPlayer?.start();
222
+ this.drumsPlayer?.start();
223
+ this.pianoPlayer?.start();
224
+ this.otherPlayer?.start();
225
}
226
}
227
@@ -252,25 +268,47 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
268
showinfo: 0
269
},
270
};
255
-
256
- let togglePlayButton = <GiPauseButton
257
- color={this.state.isPlaying ? `white` : `red`}
258
- onClick={() => this.togglePlayAudio() }
259
- style={buttonStyle} />
260
- if (this.state.isPlaying) {
261
- togglePlayButton = <GiPlayButton
262
- color={this.state.isPlaying ? `white` : `red`}
263
- onClick={() => this.togglePlayAudio() }
264
- style={buttonStyle} />
271
+ let togglePlayButton = <p>Waiting for YouTube...</p>
272
+ if (this.state.isYoutubePlayerReady) {
273
+ let button
274
+ if (this.state.isPlaying) {
275
+ button = <Col>
276
+ <GiPauseButton
277
+ color={this.state.isPlaying ? `white` : `red`}
278
+ onClick={() => this.togglePlayAudio() }
279
+ style={buttonStyle} />
280
+ </Col>
281
+ } else {
282
+ button = <Col>
283
+ <GiPlayButton
284
+ color={this.state.isPlaying ? `white` : `red`}
285
+ onClick={() => this.togglePlayAudio() }
286
+ style={buttonStyle} />
287
+ </Col>
288
+ }
289
+ togglePlayButton = <Col>
290
+ <p>
291
+ <FormattedMessage id="player.instruction"
292
+ defaultMessage="Click isntrument icon to mute part of the song."
293
+ description="Instruction message"/>
294
+ </p>
295
+ {button}
296
+ </Col>
297
}
298
299
return (
300
<IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
301
<Row className="mb-3 mt-3">
302
<Col>
271
- {togglePlayButton}
303
+ <h1>SplitFire AI</h1>
304
+ <a href="https://testflight.apple.com/join/4cb0rDIo" target={`__blank`}>
305
+ <img src='https://splitfire.ai/packs/media/images/Pre-order_on_the_App_Store_Badge_US-UK_RGB_blk_121217-5fb138d3f649c68f7b3250e3887dbef5.svg' />
306
+ </a>
307
</Col>
308
</Row>
309
+ <Row className="mb-3 mt-3">
310
+ {togglePlayButton}
311
+ </Row>
312
<Row className="mb-3 mt-3">
313
<Col>
314
<GiMicrophone
@@ -304,7 +342,8 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
342
</Col>
343
</Row>
344
<Row>
307
- <Col>
345
+ <Col style={{display: this.state.isYoutubePlayerReady ? 'block' : 'none'}}>
346
+
347
<YouTube
348
videoId={this.state.audioFile?.youtube_video_id}
349
opts={opts}
@@ -318,8 +357,16 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
357
} else {
358
return (
359
<Row>
360
+ <Row className="mb-3 mt-3">
361
+ <Col>
362
+ <h1>SplitFire AI</h1>
363
+ <a href="https://testflight.apple.com/join/4cb0rDIo" target={`__blank`}>
364
+ <img src='https://splitfire.ai/packs/media/images/Pre-order_on_the_App_Store_Badge_US-UK_RGB_blk_121217-5fb138d3f649c68f7b3250e3887dbef5.svg' />
365
+ </a>
366
+ </Col>
367
+ </Row>
368
<Col className="mb-3 mt-3">
322
- <p>Loading player...</p>
369
+ <p>🤖 AI is doing its business...</p>
370
<Spinner animation='grow'></Spinner>
371
</Col>
372
</Row>
@@ -328,14 +375,19 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
375
}
376
377
_onReady(event: any) {
331
- console.log('YouTube ready: ', event);
332
- this.setState({ isYoutubePlayerReady: true });
378
+ console.log('YouTube ready: ', event)
379
+ this.youTubeEventTarget = event.target
380
+ this.youTubeEventTarget?.playVideo()
381
+ setTimeout(() => {this.youTubeEventTarget?.pauseVideo()}, 5000)
382
}
383
384
_onStateChange(event: any) {
385
console.log('State changed: ', event)
386
console.log('getCurrentTime: ', event.target.getCurrentTime())
387
console.log('getPlayerState: ', event.target.getPlayerState())
388
+ if (!this.state.isYoutubePlayerReady && event.target.getCurrentTime() > 1) {
389
+ this.setState({ isYoutubePlayerReady: true })
390
+ }
391
}
392
}
393
src/components/Firebase/firebase.ts
+5
@@ -16,6 +16,8 @@ class Firebase {
16
db: app.database.Database;
17
language: string;
18
19
+ public onLanguageChangedCallback: (() => void) | null = null
20
+
21
constructor(language: string) {
22
app.initializeApp(config);
23
this.db = app.database();
@@ -24,6 +26,9 @@ class Firebase {
26
27
setLanguage(language: string) {
28
this.language = language
29
+
30
+ if (this.onLanguageChangedCallback)
31
+ this.onLanguageChangedCallback()
32
}
33
34
rootRef(): app.database.Reference {
src/components/FooterLinks.tsx
+5
-1
@@ -24,7 +24,11 @@ const FooterLinks = () => (
24
<footer className="text-muted py-5">
25
<div className="container">
26
<p className="float-end mb-1">
27
- <a href="#" style={linkStyle}>Back to top</a>
27
+ <a href="#" style={linkStyle}>
28
+ <FormattedMessage id="footer.to_top"
29
+ defaultMessage="Back to top"
30
+ description="To top link" />
31
+ </a>
32
</p>
33
<p className="mb-0">
34
<Link to='/about' style={linkStyle}>
src/components/Header.tsx
+14
-2
@@ -1,6 +1,7 @@
1
import React, { Component } from 'react';
2
import { FormattedMessage } from 'react-intl';
3
import { Link } from 'react-router-dom';
4
+import LanguageDropdown from './LanguageDropdown';
5
6
type HeaderProps = {
7
onChangeLanguage: (language: string) => void
@@ -33,16 +34,26 @@ class Header extends Component<HeaderProps, HeaderState> {
34
defaultMessage="About"
35
description="About page title" /></h4>
36
<p className="text-muted">We buy things we don't need, with money we don't have, to impress people we don't like.</p>
37
+ <p className="text-muted">Get your first <a href="https://www.americanexpress.se/bjudin/setoE9El2?CPID=999999550" target="_blank">American Express card</a>.</p>
38
+ <p className="text-muted">Get your proper vm, <a href="https://hetzner.cloud/?ref=WwAE3O6U53DF">Kamerad</a>.</p>
39
</div>
40
<div className="col-sm-2 offset-md-1 py-4">
38
- <h4 className="text-white">Fun projects</h4>
41
+ <h4 className="text-white">
42
+ <FormattedMessage id="header.fun"
43
+ defaultMessage="Fun projects"
44
+ description="Fun projects title"/></h4>
45
<ul className="list-unstyled">
46
<li><a className="text-white" href="https://musik88.com/" title="Musik88" target="_blank" rel="noopener noreferrer">Musik88</a></li>
47
<li><a className="text-white" href="https://splitfire.ai/" title="SplitFire AI" target="_blank" rel="noopener noreferrer">SplitFire AI</a></li>
48
+ <li><a className="text-white" href="https://smbpndk.com/" title="SumbuPendek" target="_blank" rel="noopener noreferrer">SumbuPendek</a></li>
49
+ <li><a className="text-white" href="https://scandinasia.eu/" title="SumbuPendek" target="_blank" rel="noopener noreferrer">United States of Scandinasia</a></li>
50
</ul>
51
</div>
52
<div className="col-sm-2 py-4">
45
- <h4 className="text-white">Contact</h4>
53
+ <h4 className="text-white">
54
+ <FormattedMessage id="contact.title"
55
+ defaultMessage="Contact"
56
+ description="Contact page title"/></h4>
57
<ul className="list-unstyled">
58
<li><a className="text-white" href="https://stackoverflow.com/users/1137814/seto" title="StackOverflow" target="_blank" rel="noopener noreferrer">StackOverflow</a></li>
59
<li><a className="text-white" href="https://github.com/setoelkahfi" title="GitHub" target="_blank" rel="noopener noreferrer">GitHub</a></li>
@@ -58,6 +69,7 @@ class Header extends Component<HeaderProps, HeaderState> {
69
<Link to='/' className="navbar-brand d-flex align-items-center">
70
<strong>@seto</strong>
71
</Link>
72
+ <LanguageDropdown onChangeLanguage={this.props.onChangeLanguage} />
73
<button className="navbar-toggler" onClick={this.toggleExpand.bind(this)} type="button" data-bs-toggle="collapse" data-bs-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded={this.state.expand} aria-label="Toggle navigation">
74
<span className="navbar-toggler-icon"></span>
75
</button>
src/components/Home.tsx
+30
-15
@@ -26,7 +26,10 @@ const wordStyle = {
26
const initialValues: any = {
27
"en": ["an Android developer", "an iOS developer"],
28
"id": ["pengembang aplikasi Android", "pengembang aplikasi iOS"],
29
- "se": ["är Android-utvecklare", "är iOS-utvecklare"]
29
+ "sv": ["är Android-utvecklare", "är iOS-utvecklare"],
30
+ "es": ["an Android developer", "an iOS developer"],
31
+ "de": ["an Android developer", "an iOS developer"],
32
+ "fr": ["an Android developer", "an iOS developer"],
33
};
34
35
@@ -43,7 +46,11 @@ type HomeState = {
46
47
class Home extends Component<HomeProps, HomeState> {
48
46
- whoAmIRef: app.database.Reference | undefined;
49
+ whoAmIRef: app.database.Reference | undefined
50
+
51
+ onLanguageChangedCallback = () => {
52
+ console.log("onLanguageChangedCallback")
53
+ }
54
55
constructor(props: HomeProps) {
56
super(props);
@@ -54,20 +61,12 @@ class Home extends Component<HomeProps, HomeState> {
61
alterEgos: initialValues[this.props.firebase?.language ?? 'en'],
62
files: []
63
}
64
+ if (this.props.firebase)
65
+ this.props.firebase.onLanguageChangedCallback = this.onLanguageChangedCallback.bind(this)
66
}
67
68
componentDidMount() {
60
- this.whoAmIRef?.on('value', (snapshot) => {
61
- let items = snapshot?.val();
62
- let whoAmI = [];
63
- for (let item in items) {
64
- whoAmI.push(items[item].content);
65
- }
66
- this.setState({
67
- alterEgos: this.shuffle(whoAmI)
68
- });
69
- });
70
-
69
+ this._updateTranslationIfNeeded()
70
axios.get(`/splitfire`)
71
.then(res => {
72
const files = res.data.audio_files;
@@ -77,6 +76,21 @@ class Home extends Component<HomeProps, HomeState> {
76
77
componentWillUnmount() {
78
this.whoAmIRef?.off();
79
+ if (this.props.firebase)
80
+ this.props.firebase.onLanguageChangedCallback = null
81
+ }
82
+
83
+ _updateTranslationIfNeeded() {
84
+ this.whoAmIRef?.on('value', (snapshot) => {
85
+ let items = snapshot?.val();
86
+ let whoAmI = [];
87
+ for (let item in items) {
88
+ whoAmI.push(items[item].content);
89
+ }
90
+ this.setState({
91
+ alterEgos: this.shuffle(whoAmI)
92
+ });
93
+ })
94
}
95
96
shuffle(array: any[]) {
@@ -99,9 +113,10 @@ class Home extends Component<HomeProps, HomeState> {
113
}
114
115
render() {
102
- const { files } = this.state;
116
+ const { files } = this.state
117
+
118
+ let caraoselContent: any = 'Loading SplitFire AI...'
119
104
- let caraoselContent: any = 'Loading SplitFire AI...';
120
if (files.length > 0) {
121
caraoselContent = <Carousel>
122
{files.slice(0, 3).map(item => (
src/components/LanguageDropdown.tsx
+46
-3
@@ -33,7 +33,7 @@ class LanguageDropdown extends Component<LanguageDropdownProps, LanguageDropdown
33
name: "🇺🇸 English"
34
}
35
36
- const sv = {
36
+ const se = {
37
value: "se",
38
name: "🇸🇪 Swedish"
39
}
@@ -43,12 +43,55 @@ class LanguageDropdown extends Component<LanguageDropdownProps, LanguageDropdown
43
name: "🇮🇩 Indonesian"
44
}
45
46
+ const fr = {
47
+ value: "fr",
48
+ name: "🇫🇷 Français"
49
+ }
50
+
51
+ const de = {
52
+ value: "de",
53
+ name: "🇩🇪 Deutsch"
54
+ }
55
+
56
+ const zh = {
57
+ value: "zh",
58
+ name: "🇨🇳 中国人"
59
+ }
60
+
61
+ const es = {
62
+ value: "es",
63
+ name: "🇪🇸 Español"
64
+ }
65
+
66
var languageList: OptionProps[] = []
67
languageList.push(id)
48
- languageList.push(sv)
68
+ languageList.push(se)
69
languageList.push(en)
70
+ languageList.push(de)
71
+ languageList.push(fr)
72
+ languageList.push(zh)
73
+ languageList.push(es)
74
+
75
+ return this.shuffle(languageList)
76
+ }
77
+
78
+ shuffle(array: any[]) {
79
+ var currentIndex = array.length, temporaryValue, randomIndex;
80
+
81
+ // While there remain elements to shuffle...
82
+ while (0 !== currentIndex) {
83
+
84
+ // Pick a remaining element...
85
+ randomIndex = Math.floor(Math.random() * currentIndex);
86
+ currentIndex -= 1;
87
+
88
+ // And swap it with the current element.
89
+ temporaryValue = array[currentIndex];
90
+ array[currentIndex] = array[randomIndex];
91
+ array[randomIndex] = temporaryValue;
92
+ }
93
51
- return languageList
94
+ return array;
95
}
96
97
render() {
src/components/Main.tsx
+1
-1
@@ -18,7 +18,7 @@ const mainStyle = {
18
19
const contentStyle = {
20
borderRadius: '100px!important',
21
- backgroundColor: 'rgba(52, 52, 52, 0.5)'
21
+ backgroundColor: 'rgba(52, 52, 52, 0.6)'
22
}
23
24
const Main = () => (
src/translations/de.json
new
+15
@@ -0,0 +1,15 @@
1
+{
2
+ "header.home": "Heim",
3
+ "header.about": "Etwa",
4
+ "header.cv": "CV",
5
+ "header.contact": "Kontakt",
6
+ "header.fun": "Lustige Projekte",
7
+ "home.title": "Hallo, mein Name ist {name}.",
8
+ "home.iam": "Ich bin ",
9
+ "about.title": "Etwa",
10
+ "about.skills": "Fähigkeiten",
11
+ "cv.title": "CV",
12
+ "contact.title": "Kontakt",
13
+ "player.instruction": "Klicken Sie auf das Instrumentensymbol, um es aus dem Song zu entfernen.",
14
+ "footer.to_top": "Zurück nach oben"
15
+}
src/translations/en.json
+4
-1
@@ -3,10 +3,13 @@
3
"header.about": "About",
4
"header.cv": "CV",
5
"header.contact": "Contact",
6
+ "header.fun": "Fun projects",
7
"home.title": "Hello, my name is {name}.",
8
"home.iam": "I'm ",
9
"about.title": "About",
10
"about.skills": "Skills",
11
"cv.title": "CV",
11
- "contact.title": "Contact"
12
+ "contact.title": "Contact",
13
+ "player.instruction": "Click the instrument icon to remove it from the song.",
14
+ "footer.to_top": "Back to top"
15
}
src/translations/es.json
new
+15
@@ -0,0 +1,15 @@
1
+{
2
+ "header.home": "Casa",
3
+ "header.about": "Sobre",
4
+ "header.cv": "CV",
5
+ "header.contact": "Contacto",
6
+ "header.fun": "Proyectos divertidos",
7
+ "home.title": "Hola, mi nombre es {name}.",
8
+ "home.iam": "Estoy ",
9
+ "about.title": "Acerca de",
10
+ "about.skills": "Habilidades",
11
+ "cv.title": "CV",
12
+ "contact.title": "Contacto",
13
+ "player.instruction": "Haz clic en el ícono del instrumento para eliminarlo de la canción.",
14
+ "footer.to_top": "Volver arriba"
15
+}
src/translations/fr.json
new
+15
@@ -0,0 +1,15 @@
1
+{
2
+ "header.home": "Accueil",
3
+ "header.about": "Sur",
4
+ "header.cv": "CV",
5
+ "header.contact": "Contact",
6
+ "header.fun": "Projets amusants",
7
+ "home.title": "Bonjour, je m'appelle {name}.",
8
+ "home.iam": "Je suis ",
9
+ "about.title": "Sur",
10
+ "about.skills": "Compétences",
11
+ "cv.title": "CV",
12
+ "contact.title": "Contact",
13
+ "player.instruction": "Cliquez sur l'icône de l'instrument pour le supprimer du morceau.",
14
+ "footer.to_top": "Retour en haut"
15
+}
src/translations/id.json
+4
-1
@@ -3,10 +3,13 @@
3
"header.about": "Tentang",
4
"header.cv": "CV",
5
"header.contact": "Kontak",
6
+ "header.fun": "Iseng-iseng berhadiah",
7
"home.title": "Halo, nama saya {name}.",
8
"home.iam": "Saya ",
9
"about.title": "Tentang",
10
"about.skills": "Keterampilan",
11
"cv.title": "CV",
11
- "contact.title": "Kontak"
12
+ "contact.title": "Kontak",
13
+ "player.instruction": "Klik ikon instrumen untuk menghapusnya dari lagu.",
14
+ "footer.to_top": "Kembali ke atas"
15
}
src/translations/sv.json
renamed
+5
-2
@@ -3,10 +3,13 @@
3
"header.about": "Om",
4
"header.cv": "CV",
5
"header.contact": "Kontakt",
6
+ "header.fun": "Roliga projekt",
7
"home.title": "Tjena, jag heter {name}.",
8
"home.iam": "Jag ",
9
"about.title": "Om",
10
"about.skills": "Kompetens",
11
"cv.title": "CV",
11
- "contact.title": "Kontakt"
12
- }
12
+ "contact.title": "Kontakt",
13
+ "player.instruction": "Klicka på instrumentikonen för att ta bort den från låten.",
14
+ "footer.to_top": "Tillbaka till toppen"
15
+ }
\ No newline at end of file
src/translations/zh.json
new
+15
@@ -0,0 +1,15 @@
1
+{
2
+ "header.home": "家",
3
+ "header.about": "关于",
4
+ "header.cv": "CV",
5
+ "header.contact": "接触",
6
+ "header.fun": "有趣的项目",
7
+ "home.title": "你好我的名字是 {name}.",
8
+ "home.iam": "我是",
9
+ "about.title": "关于",
10
+ "about.skills": "技能",
11
+ "cv.title": "CV",
12
+ "contact.title": "接触",
13
+ "player.instruction": "点击乐器图标将其从歌曲中移除。",
14
+ "footer.to_top": "回到顶部"
15
+}
types/react-intl/index.d.ts
+5
-1
@@ -1,4 +1,8 @@
1
declare module 'react-intl';
2
declare module 'react-intl/locale-data/se';
3
declare module 'react-intl/locale-data/en';
4
-declare module 'react-intl/locale-data/id';
\ No newline at end of file
4
+declare module 'react-intl/locale-data/id';
5
+declare module 'react-intl/locale-data/de';
6
+declare module 'react-intl/locale-data/fr';
7
+declare module 'react-intl/locale-data/zh';
8
+declare module 'react-intl/locale-data/es';
\ No newline at end of file