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

add translations #3

Merged
merged 7 commits into from
Dec 27, 2023
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
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: 'npm' # See documentation for possible values
directory: '/' # Location of package manifests
target-branch: 'dev'
schedule:
interval: 'weekly'
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# region-adjacency-checker
Checks whether two regions touch each other
Checks whether two regions touch each other.
5,116 changes: 910 additions & 4,206 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 12 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,24 @@
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@picocss/pico": "^1.5.10",
"pinia": "^2.1.6",
"sass": "^1.64.1",
"vue": "^3.3.4"
"pinia": "^2.1.7",
"sass": "^1.69.5",
"vue": "^3.3.13",
"vue-i18n": "^9.8.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.2",
"@tsconfig/node18": "^18.2.0",
"@types/node": "^18.17.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/tsconfig": "^0.4.0",
"eslint": "^8.45.0",
"eslint-plugin-vue": "^9.15.1",
"@tsconfig/node20": "^20.1.2",
"@types/node": "^20.10.5",
"@vitejs/plugin-vue": "^5.0.0",
"@vue/tsconfig": "^0.5.1",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.0",
"typescript": "~5.1.6",
"vite": "^4.4.6",
"vue-tsc": "^1.8.6"
"prettier": "^3.1.1",
"typescript": "^5.3.3",
"vite": "^5.0.10",
"vue-tsc": "^1.8.27"
}
}
21 changes: 13 additions & 8 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { storeToRefs } from 'pinia';
import GlyphInput from './components/GlyphInput.vue';
import { ref } from 'vue';
import { useRegionAdjacency } from './composables/useRegionAdjacency';
import { useI18n } from './hooks/useI18n';

const { t } = useI18n();

const regionDataStore = useRegionDataStore();
const { glyphValues } = storeToRefs(regionDataStore);
Expand All @@ -15,13 +18,13 @@ const isAdjacent = ref(false);
function checkAdjacency() {
const distance = useRegionAdjacency();
if (distance === 1) {
adjacency.value = 'Directly touching';
adjacency.value = t('translation.directlytouching');
} else if (distance === Math.sqrt(2)) {
adjacency.value = 'Touching on one edge';
adjacency.value = t('translation.touchingontheedge');
} else if (distance === Math.sqrt(3)) {
adjacency.value = 'Touching on one corner';
adjacency.value = t('translation.touchingonthecorner');
} else {
adjacency.value = 'Not touching';
adjacency.value = t('translation.touchingonthecorner');
}
isAdjacent.value = distance === 1 || distance === Math.sqrt(2) || distance === Math.sqrt(3);
}
Expand All @@ -30,29 +33,31 @@ function checkAdjacency() {
<template>
<header>
<NavBar />
<h1 class="title">Region Adjacency Checker</h1>
<h1 class="title">{{ t('translation.title') }}</h1>
</header>

<main>
<div class="input-wrapper">
<GlyphInput
:index="0"
:label="t('translation.enterfirstregion')"
class="glyph-input"
label="Enter First Region Glyphs/Coordinates"
maxlength="12"
/>

<GlyphInput
:index="1"
:label="t('translation.entersecondregion')"
class="glyph-input"
label="Enter Second Region Glyphs/Coordinates"
maxlength="12"
/>
</div>
<button
:disabled="!glyphValues.length || glyphValues.some((gl) => gl.length !== 12)"
class="button"
@click="checkAdjacency"
>
Check Adjacency
{{ t('translation.check') }}
</button>
<p
v-show="adjacency"
Expand Down
11 changes: 7 additions & 4 deletions src/components/GlyphInput.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<script setup lang="ts">
import { useRegionDataStore } from '../stores/regionData';
import { storeToRefs } from 'pinia';
import { useI18n } from '../hooks/useI18n';

const { t } = useI18n();

const props = defineProps<{
label: string;
Expand Down Expand Up @@ -46,8 +49,8 @@ const numberToGlyph = (n: number) => n.toString(16).toUpperCase(); // NoSonar th
<input
class="glyphs-input"
id="portalglyphsInput"
type="text"
maxlength="19"
type="text"
v-model="glyphs[index]"
@input="lintGlyphs"
/>
Expand All @@ -58,16 +61,16 @@ const numberToGlyph = (n: number) => n.toString(16).toUpperCase(); // NoSonar th
type="button"
@click="deleteGlyph"
>
&larr; Delete
&larr; {{ t('translation.delete') }}
</button>
</div>
<div class="portal-buttons grid">
<button
v-for="n in 16"
class="button glyphs"
type="button"
:id="'glyphButton' + n"
:value="numberToGlyph(n - 1)"
class="button glyphs"
type="button"
@click="addGlyph"
>
{{ numberToGlyph(n - 1) }}
Expand Down
30 changes: 29 additions & 1 deletion src/components/NavBar.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,43 @@
<script setup lang="ts">
import { watch, ref } from 'vue';
import { useI18n } from '../hooks/useI18n';
import ThemeSwitch from './ThemeSwitch.vue';

const { t, locale } = useI18n();

type Locales = typeof locale.value;

const selectedLocale = ref<Locales>(locale.value);

watch(selectedLocale, (newVal) => {
locale.value = newVal;
localStorage.setItem('lang', newVal);
});
</script>

<template>
<nav>
<ul>
<li>
<a href="..">&larr; View other pages</a>
<a
:title="t('translation.viewother')"
href=".."
>← {{ t('translation.viewother') }}</a
>
</li>
</ul>
<ul>
<li>
<select v-model="selectedLocale">
<option
v-for="locale in $i18n.availableLocales"
:key="`locale-${locale}`"
:value="locale"
>
{{ locale }}
</option>
</select>
</li>
<li>
<ThemeSwitch />
</li>
Expand Down
7 changes: 5 additions & 2 deletions src/components/ThemeSwitch.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<script setup lang="ts">
import { useI18n } from '../hooks/useI18n';

const { t } = useI18n();
// determine preferred theme and update the html element with the respective tag
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
switchTheme(prefersDark ? 'dark' : 'light');
Expand All @@ -13,11 +16,11 @@ function switchTheme(theme: string | undefined = undefined) {

<template>
<button
role="button"
class="themeswitcher"
id="themeSwitch"
role="button"
@click="switchTheme()"
>
Switch Theme
{{ t('translation.switchtheme') }}
</button>
</template>
4 changes: 2 additions & 2 deletions src/composables/useRegionAdjacency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useRegionDataStore } from '../stores/regionData';
import { storeToRefs } from 'pinia';

const extractX = (glyphs: string) => glyphs.slice(-3);
const extractY = (glyphs: string) => glyphs.slice(-6, -3);
const extractZ = (glyphs: string) => glyphs.slice(4, 6);
const extractY = (glyphs: string) => glyphs.slice(4, 6);
const extractZ = (glyphs: string) => glyphs.slice(-6, -3);

export function useRegionAdjacency() {
const regionDataStore = useRegionDataStore();
Expand Down
39 changes: 39 additions & 0 deletions src/hooks/useI18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { i18n, messages } from '../i18n';

// inspired by https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object

type Join<FirstType, SecondType> = FirstType extends string | number
? SecondType extends string | number
? `${FirstType}${'' extends SecondType ? '' : '.'}${SecondType}`
: never
: never;

/**
* Helper type that transforms an object tree into a union type of all possibles leaves.
*/
type Leaves<ObjectType> = ObjectType extends Record<string, unknown>
? // eslint-disable-next-line @typescript-eslint/no-unused-vars
{ [Key in keyof ObjectType]-?: Join<Key, Leaves<ObjectType[Key]>> }[keyof ObjectType]
: '';

export type I18NLeaves = Leaves<(typeof messages)['Español']>;

// This function adds type safety to the i18n t function.
export function useI18n() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const { t, te, d, n, tm, rt, ...globalApi } = i18n.global;

type RemoveFirstFromTuple<T extends unknown[]> = ((...b: T) => void) extends (...b: infer I) => void ? I : [];

const typedT = t as (...args: [I18NLeaves, ...Partial<RemoveFirstFromTuple<Parameters<typeof t>>>]) => string;

return {
t: typedT.bind(i18n),
d: d.bind(i18n),
te: te.bind(i18n),
tm: tm.bind(i18n),
rt: rt.bind(i18n),
n: n.bind(i18n),
...globalApi,
};
}
5 changes: 5 additions & 0 deletions src/i18n/de-DE/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import translation from './translation';

export default {
translation,
};
13 changes: 13 additions & 0 deletions src/i18n/de-DE/translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
viewother: 'Andere Seiten ansehen',
switchtheme: 'Farbschema wechseln',
title: 'Region Angrenzungs Tester',
directlytouching: 'Berühren sich direkt',
touchingontheedge: 'Berühren sich an einer Kante',
touchingonthecorner: 'Berühren sich an einer Ecke',
nottouching: 'Berühren sich nicht',
enterfirstregion: 'Glyphen/Koordinaten der ersten Region',
entersecondregion: 'Glyphen/Koordinaten der zweiten Region',
delete: 'Löschen',
check: 'Angrenzung testen',
};
5 changes: 5 additions & 0 deletions src/i18n/en-EN/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import translation from './translation';

export default {
translation,
};
13 changes: 13 additions & 0 deletions src/i18n/en-EN/translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
viewother: 'View other pages',
switchtheme: 'Switch Theme',
title: 'Region Adjacency Checker',
directlytouching: 'Directly touching',
touchingontheedge: 'Touching on one edge',
touchingonthecorner: 'Touching on one corner',
nottouching: 'Not touching',
enterfirstregion: 'Enter First Region Glyphs/Coordinates',
entersecondregion: 'Enter Second Region Glyphs/Coordinates',
delete: 'Delete',
check: 'Check Adjacency',
};
5 changes: 5 additions & 0 deletions src/i18n/es-ES/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import translation from './translation';

export default {
translation,
};
13 changes: 13 additions & 0 deletions src/i18n/es-ES/translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
viewother: 'Ver otras páginas',
switchtheme: 'Cambiar tema',
title: 'Comprobador de regiones adyacentes',
directlytouching: 'Tocando directamente',
touchingontheedge: 'Tocando en un borde',
touchingonthecorner: 'Tocando en una esquina',
nottouching: 'No se tocan',
enterfirstregion: 'Ingrese los glifos/coordenadas de la primera región',
entersecondregion: 'Ingrese los glifos/coordenadas de la segunda región',
delete: 'Borrar',
check: 'Comprobar adyacencia',
};
5 changes: 5 additions & 0 deletions src/i18n/eu-EU/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import translation from './translation';

export default {
translation,
};
13 changes: 13 additions & 0 deletions src/i18n/eu-EU/translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
viewother: 'Ikusi beste orrialde batzuk',
switchtheme: 'Itxura aldatu',
title: 'Inguruko eskualdeen egiaztagailua',
directlytouching: 'Zuzenean ukituz',
touchingontheedge: 'Ertz batean ukitzen',
touchingonthecorner: 'Izkina batean jotzen',
nottouching: 'Ez dira ukitzen',
enterfirstregion: 'Sartu lehen eskualdeko glifoak/koordenatuak',
entersecondregion: 'Sartu bigarren eskualdeko glifoak/koordenatuak',
delete: 'Ezabatu',
check: 'Egiaztatu albokotasuna',
};
35 changes: 35 additions & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createI18n } from 'vue-i18n';
import Español from './es-ES';
import English from './en-EN';
import Euskara from './eu-EU';
import Deutsch from './de-DE';

const messages = {
Español,
English,
Euskara,
Deutsch,
};

const languageMap = {
Español: ['es', 'es-ES'],
English: ['en', 'en-EN', 'en-GB', 'en-US'],
Euskara: ['eu', 'eu-EU'],
Deutsch: ['de', 'de-DE'],
};

const langEntries = Object.entries(languageMap);
const preferredUserLang = navigator.language;

const userLang = langEntries.find((lang) => lang[1].some((langCode) => langCode === preferredUserLang))?.[0] ?? 'English';

const localStorageLang = localStorage.getItem('lang');
const preferredLang = localStorageLang ?? userLang;

const i18n = createI18n({
locale: preferredLang,
legacy: false,
messages,
});

export { i18n, messages };
Loading
Loading