Skip to content

Commit d8d564b

Browse files
authored
Merge pull request #11 from Muhaddil/main
Use Vue-I18n for language translations
2 parents b0b42e3 + 6664a38 commit d8d564b

18 files changed

+312
-27
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# image-compressor
2-
Compresses Images to below 10MB
2+
Compresses Images to below 10MB.

package-lock.json

Lines changed: 77 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"jszip": "^3.10.1",
1818
"pinia": "^2.1.6",
1919
"sass": "^1.64.1",
20-
"vue": "^3.3.4"
20+
"vue": "^3.3.4",
21+
"vue-i18n": "^9.8.0"
2122
},
2223
"devDependencies": {
2324
"@tsconfig/node20": "^20.1.2",

src/App.vue

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
2-
import NavBar from './components/NavBar.vue';
2+
import ThemeSwitch from './components/ThemeSwitch.vue';
3+
import Nav from './components/Nav.vue';
34
import FileUpload from './components/FileUpload.vue';
45
import FileItem from './components/FileItem.vue';
56
import { useFileDataStore } from './stores/fileData';
@@ -59,33 +60,34 @@ watch(anyUncompressed, async (newVal) => {
5960

6061
<template>
6162
<header>
62-
<NavBar />
63-
<h1 class="title">Image Compressor</h1>
63+
<Nav />
64+
<ThemeSwitch />
65+
<h1 class="title">{{ $t('translation.header') }}</h1>
6466
</header>
6567

6668
<main>
6769
<div class="explanation-wrapper">
68-
<p class="explanation">Compresses images to &lt; 10MB.</p>
70+
<p class="explanation">{{ $t('translation.subtitle') }}</p>
6971
<a
7072
href="https://nomanssky.fandom.com/wiki/Special:Upload?multiple=true"
7173
role="button"
7274
target="_blank"
7375
rel="noopener noreferrer"
74-
>Open NMS Wiki Image Upload</a
76+
>{{ $t('translation.buttonwiki') }}</a
7577
>
7678
</div>
77-
<h2 class="subheading">Input</h2>
79+
<h2 class="subheading">{{ $t('translation.input') }}</h2>
7880
<FileUpload />
7981

80-
<h2 class="subheading">File List</h2>
82+
<h2 class="subheading">{{ $t('translation.filelist') }}</h2>
8183
<div class="buttons">
8284
<button
8385
:aria-busy="isCompressing"
8486
:class="{ 'is-success': files.length && !anyUncompressed }"
8587
:disabled="!files.length || !anyUncompressed"
8688
@click="compressFiles"
8789
>
88-
{{ files.length && !anyUncompressed ? 'All compressed!' : 'Compress' }}
90+
{{ files.length && !anyUncompressed ? $t('translation.allcompressed') : $t('translation.compress') }}
8991
</button>
9092
<a
9193
:aria-busy="isZipCompressing"
@@ -94,14 +96,14 @@ watch(anyUncompressed, async (newVal) => {
9496
role="button"
9597
download
9698
>
97-
Download ZIP
99+
{{ $t('translation.downloadzip') }}
98100
</a>
99101
<button
100102
:disabled="!files.length"
101103
class="secondary"
102104
@click="files = []"
103105
>
104-
Clear List
106+
{{ $t('translation.clearlist') }}
105107
</button>
106108
</div>
107109
<div class="file-list">

src/components/FileItem.vue

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,20 @@ const computeFileSize = (size: number) =>
3939
/>
4040
</a>
4141
<div>
42-
<div><span class="field-title">Name:</span> {{ fileObj.file.name }}</div>
43-
<div><span class="field-title">Original Size:</span> {{ computeFileSize(orgSize) }}MB</div>
44-
<div v-if="compSize"><span class="field-title">Compressed Size:</span> {{ computeFileSize(compSize) }}MB</div>
42+
<div>
43+
<span class="field-title">{{ $t('translation.name') }}</span> {{ fileObj.file.name }}
44+
</div>
45+
<div>
46+
<span class="field-title">{{ $t('translation.originalsize') }}</span> {{ computeFileSize(orgSize) }}MB
47+
</div>
48+
<div v-if="compSize">
49+
<span class="field-title">{{ $t('translation.compressedsize') }}</span> {{ computeFileSize(compSize) }}MB
50+
</div>
4551
<div
4652
v-if="fileObj.isTooLarge"
4753
class="error"
4854
>
49-
<span class="field-title">Error:</span> File is too large!
55+
<span class="field-title">{{ $t('translation.error') }}</span> {{ $t('translation.filetoolarge') }}
5056
</div>
5157
</div>
5258
<a
@@ -55,7 +61,7 @@ const computeFileSize = (size: number) =>
5561
:href="fileObj.isCompressed ? fileData : undefined"
5662
role="button"
5763
download
58-
>Download</a
64+
>{{ $t('translation.download') }}</a
5965
>
6066
<button
6167
class="secondary delete-button"

src/components/FileUpload.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function addFiles(uploadedFiles: FileList) {
4747
@drop.prevent="dropFile"
4848
@dragover.prevent
4949
>
50-
<span class="drop-title">Drop files here</span>
50+
<span class="drop-title">{{ $t('translation.dropfiles') }}</span>
5151
<input
5252
type="file"
5353
id="fileUpload"

src/components/Nav.vue

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<template>
2+
<nav>
3+
<ul>
4+
<li>
5+
<a
6+
href=".."
7+
title="Other pages"
8+
>← {{ $t('translation.viewother') }}</a
9+
>
10+
</li>
11+
</ul>
12+
<ul>
13+
<li>
14+
<select v-model="selectedLocale">
15+
<option
16+
v-for="locale in $i18n.availableLocales"
17+
:key="`locale-${locale}`"
18+
:value="locale"
19+
>
20+
{{ locale }}
21+
</option>
22+
</select>
23+
</li>
24+
</ul>
25+
</nav>
26+
</template>
27+
28+
<script setup lang="ts">
29+
import { watch, ref } from 'vue';
30+
import { useI18n } from '../hooks/useI18n';
31+
32+
const { locale } = useI18n();
33+
34+
type Locales = typeof locale.value;
35+
36+
const selectedLocale = ref<Locales>(locale.value);
37+
38+
watch(selectedLocale, (newVal) => {
39+
locale.value = newVal;
40+
localStorage.setItem('lang', newVal);
41+
});
42+
</script>

src/components/ThemeSwitch.vue

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ function switchTheme(theme: string | undefined = undefined) {
1212
</script>
1313

1414
<template>
15-
<button
16-
role="button"
17-
class="themeswitcher"
18-
id="themeSwitch"
19-
@click="switchTheme()"
20-
>
21-
Switch Theme
22-
</button>
15+
<div style="text-align: right">
16+
<button
17+
role="button"
18+
class="themeswitcher"
19+
id="themeSwitch"
20+
@click="switchTheme()"
21+
style="width: auto"
22+
>
23+
{{ $t('translation.switchtheme') }}
24+
</button>
25+
</div>
2326
</template>

src/hooks/useI18n.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { i18n, messages } from '../i18n';
2+
3+
// inspired by https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object
4+
5+
type Join<FirstType, SecondType> = FirstType extends string | number
6+
? SecondType extends string | number
7+
? `${FirstType}${'' extends SecondType ? '' : '.'}${SecondType}`
8+
: never
9+
: never;
10+
11+
/**
12+
* Helper type that transforms an object tree into a union type of all possibles leaves.
13+
*/
14+
type Leaves<ObjectType> = ObjectType extends Record<string, unknown>
15+
? // eslint-disable-next-line @typescript-eslint/no-unused-vars
16+
{ [Key in keyof ObjectType]-?: Join<Key, Leaves<ObjectType[Key]>> }[keyof ObjectType]
17+
: '';
18+
19+
export type I18NLeaves = Leaves<(typeof messages)['Español']>;
20+
21+
// This function adds type safety to the i18n t function.
22+
export function useI18n() {
23+
// eslint-disable-next-line @typescript-eslint/unbound-method
24+
const { t, te, d, n, tm, rt, ...globalApi } = i18n.global;
25+
26+
type RemoveFirstFromTuple<T extends unknown[]> = ((...b: T) => void) extends (...b: infer I) => void ? I : [];
27+
28+
const typedT = t as (...args: [I18NLeaves, ...Partial<RemoveFirstFromTuple<Parameters<typeof t>>>]) => string;
29+
30+
return {
31+
t: typedT.bind(i18n),
32+
d: d.bind(i18n),
33+
te: te.bind(i18n),
34+
tm: tm.bind(i18n),
35+
rt: rt.bind(i18n),
36+
n: n.bind(i18n),
37+
...globalApi,
38+
};
39+
}

src/i18n/en-EN/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import translation from './translation';
2+
3+
export default {
4+
translation,
5+
};

0 commit comments

Comments
 (0)