mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-08 15:15:02 -04:00
i18n added language selector
Added language selector and some translations.
This commit is contained in:
parent
bcb98b359c
commit
fcf79cc248
11 changed files with 110 additions and 28 deletions
|
@ -1,3 +1,26 @@
|
||||||
home:
|
home:
|
||||||
|
commandPalette:
|
||||||
|
search: Search...
|
||||||
|
placeholder: Type to search a tool or a command...
|
||||||
|
commandPaletteStore:
|
||||||
|
actions: Actions
|
||||||
|
external: External
|
||||||
|
tools: Tools
|
||||||
categories:
|
categories:
|
||||||
newestTools: "Newest tools"
|
newestTools: Newest tools
|
||||||
|
title: IT Tools - Handy online tools for developers
|
||||||
|
allTools: All the tools
|
||||||
|
favoriteTools: Your favorite tools
|
||||||
|
thanks: '! Thank you'
|
||||||
|
giveUsAStarOn: Give us a star on
|
||||||
|
orFollowUsOn: or follow us on
|
||||||
|
youLikeItTools: You like it-tools?
|
||||||
|
tools:
|
||||||
|
xmlFormat:
|
||||||
|
description: Prettify your XML string to a human friendly readable format.
|
||||||
|
indentSize: 'Indent size:'
|
||||||
|
collapseContent: 'Collapse content:'
|
||||||
|
providedXmlIsNotValid: Provided XML is not valid.
|
||||||
|
inputLabel: Your XML
|
||||||
|
outputLabel: Formatted XML from your XML
|
||||||
|
placeHolder: Paste your XML here...
|
||||||
|
|
26
locales/es.yml
Normal file
26
locales/es.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
home:
|
||||||
|
commandPalette:
|
||||||
|
search: Buscar...
|
||||||
|
placeholder: Escribe para buscar una herramienta o comando...
|
||||||
|
commandPaletteStore:
|
||||||
|
actions: Acciones
|
||||||
|
external: Externa
|
||||||
|
tools: Herramientas
|
||||||
|
categories:
|
||||||
|
newestTools: Herramientas Nuevas
|
||||||
|
title: 'IT Tools - Prácticas herramientas en línea para desarrolladores.'
|
||||||
|
allTools: Todas las herramientas
|
||||||
|
favoriteTools: Tus herramientas favorias
|
||||||
|
thanks: '! Thank you'
|
||||||
|
giveUsAStarOn: Give us a star on
|
||||||
|
orFollowUsOn: or follow us on
|
||||||
|
youLikeItTools: You like it-tools?
|
||||||
|
tools:
|
||||||
|
xmlFormat:
|
||||||
|
description: Embellece tu XML a un formato XML más amigable.
|
||||||
|
indentSize: 'Tamaño de indentación:'
|
||||||
|
collapseContent: 'Colapsar contenido:'
|
||||||
|
providedXmlIsNotValid: El XML proporcionado no es válido.
|
||||||
|
inputLabel: Tu XML
|
||||||
|
outputLabel: XML Formateado desde tu XML
|
||||||
|
placeHolder: Pega tu XML aquí...
|
|
@ -8,6 +8,8 @@ const props = defineProps<{ tool: Tool & { category: string } }>();
|
||||||
const { tool } = toRefs(props);
|
const { tool } = toRefs(props);
|
||||||
const theme = useThemeVars();
|
const theme = useThemeVars();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const appTheme = useAppTheme();
|
const appTheme = useAppTheme();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ const appTheme = useAppTheme();
|
||||||
|
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<n-ellipsis :line-clamp="2" :tooltip="false" style="min-height: 44.78px">
|
<n-ellipsis :line-clamp="2" :tooltip="false" style="min-height: 44.78px">
|
||||||
{{ tool.description }}
|
{{ t(tool.description) }}
|
||||||
<br>
|
<br>
|
||||||
</n-ellipsis>
|
</n-ellipsis>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Heart, Home2, Menu2 } from '@vicons/tabler';
|
||||||
import HeroGradient from '../assets/hero-gradient.svg?component';
|
import HeroGradient from '../assets/hero-gradient.svg?component';
|
||||||
import MenuLayout from '../components/MenuLayout.vue';
|
import MenuLayout from '../components/MenuLayout.vue';
|
||||||
import NavbarButtons from '../components/NavbarButtons.vue';
|
import NavbarButtons from '../components/NavbarButtons.vue';
|
||||||
|
import { availableLocales, loadLanguageAsync } from '../plugins/i18n.plugin';
|
||||||
import { toolsByCategory } from '@/tools';
|
import { toolsByCategory } from '@/tools';
|
||||||
import { useStyleStore } from '@/stores/style.store';
|
import { useStyleStore } from '@/stores/style.store';
|
||||||
import { config } from '@/config';
|
import { config } from '@/config';
|
||||||
|
@ -24,10 +25,19 @@ const { tracker } = useTracker();
|
||||||
|
|
||||||
const toolStore = useToolStore();
|
const toolStore = useToolStore();
|
||||||
|
|
||||||
|
const currentLang = useStorage('application:selected-language', 'en');
|
||||||
|
|
||||||
|
loadLanguageAsync(currentLang.value);
|
||||||
|
|
||||||
const tools = computed<ToolCategory[]>(() => [
|
const tools = computed<ToolCategory[]>(() => [
|
||||||
...(toolStore.favoriteTools.length > 0 ? [{ name: 'Your favorite tools', components: toolStore.favoriteTools }] : []),
|
...(toolStore.favoriteTools.length > 0 ? [{ name: 'Your favorite tools', components: toolStore.favoriteTools }] : []),
|
||||||
...toolsByCategory,
|
...toolsByCategory,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function onChange(event: any) {
|
||||||
|
currentLang.value = event;
|
||||||
|
loadLanguageAsync(event);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -106,6 +116,15 @@ const tools = computed<ToolCategory[]>(() => [
|
||||||
|
|
||||||
<command-palette mx-2 />
|
<command-palette mx-2 />
|
||||||
|
|
||||||
|
<n-select
|
||||||
|
class="i18n"
|
||||||
|
:model:value="currentLang"
|
||||||
|
:options="availableLocales.map(lang => ({ value: lang, label: lang }))"
|
||||||
|
placeholder="Select language"
|
||||||
|
:on-update-value="onChange"
|
||||||
|
:default-value="currentLang"
|
||||||
|
/>
|
||||||
|
|
||||||
<NavbarButtons v-if="!styleStore.isSmallScreen" />
|
<NavbarButtons v-if="!styleStore.isSmallScreen" />
|
||||||
|
|
||||||
<n-tooltip trigger="hover">
|
<n-tooltip trigger="hover">
|
||||||
|
@ -223,4 +242,7 @@ const tools = computed<ToolCategory[]>(() => [
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.i18n{
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import type { Tool } from '@/tools/tools.types';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const head = computed<HeadObject>(() => ({
|
const head = computed<HeadObject>(() => ({
|
||||||
title: `${route.meta.name} - IT Tools`,
|
title: `${route.meta.name} - IT Tools`,
|
||||||
meta: [
|
meta: [
|
||||||
|
@ -42,7 +43,7 @@ useHead(head);
|
||||||
<div class="separator" />
|
<div class="separator" />
|
||||||
|
|
||||||
<div class="description">
|
<div class="description">
|
||||||
{{ route.meta.description }}
|
{{ t(route.meta.description) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const useCommandPaletteStore = defineStore('command-palette', () => {
|
||||||
...tool,
|
...tool,
|
||||||
to: tool.path,
|
to: tool.path,
|
||||||
toolCategory: tool.category,
|
toolCategory: tool.category,
|
||||||
category: 'Tools',
|
category: 'home.commandPaletteStore.tools',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const searchOptions: PaletteOption[] = [
|
const searchOptions: PaletteOption[] = [
|
||||||
|
@ -28,13 +28,13 @@ export const useCommandPaletteStore = defineStore('command-palette', () => {
|
||||||
description: 'Toggle dark mode on or off.',
|
description: 'Toggle dark mode on or off.',
|
||||||
action: () => styleStore.toggleDark(),
|
action: () => styleStore.toggleDark(),
|
||||||
icon: SunIcon,
|
icon: SunIcon,
|
||||||
category: 'Actions',
|
category: 'home.commandPaletteStore.actions',
|
||||||
keywords: ['dark', 'theme', 'toggle', 'mode', 'light', 'system'],
|
keywords: ['dark', 'theme', 'toggle', 'mode', 'light', 'system'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Github repository',
|
name: 'Github repository',
|
||||||
href: 'https://github.com/CorentinTh/it-tools',
|
href: 'https://github.com/CorentinTh/it-tools',
|
||||||
category: 'External',
|
category: 'home.commandPaletteStore.external',
|
||||||
description: 'View the source code of it-tools on Github.',
|
description: 'View the source code of it-tools on Github.',
|
||||||
keywords: ['github', 'repo', 'repository', 'source', 'code'],
|
keywords: ['github', 'repo', 'repository', 'source', 'code'],
|
||||||
icon: GithubIcon,
|
icon: GithubIcon,
|
||||||
|
@ -43,7 +43,7 @@ export const useCommandPaletteStore = defineStore('command-palette', () => {
|
||||||
name: 'Report a bug or an issue',
|
name: 'Report a bug or an issue',
|
||||||
description: 'Report a bug or an issue to help improve it-tools.',
|
description: 'Report a bug or an issue to help improve it-tools.',
|
||||||
href: 'https://github.com/CorentinTh/it-tools/issues/new/choose',
|
href: 'https://github.com/CorentinTh/it-tools/issues/new/choose',
|
||||||
category: 'Actions',
|
category: 'home.commandPaletteStore.actions',
|
||||||
keywords: ['report', 'issue', 'bug', 'problem', 'error'],
|
keywords: ['report', 'issue', 'bug', 'problem', 'error'],
|
||||||
icon: BugIcon,
|
icon: BugIcon,
|
||||||
},
|
},
|
||||||
|
@ -59,7 +59,11 @@ export const useCommandPaletteStore = defineStore('command-palette', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredSearchResult = computed(() =>
|
const filteredSearchResult = computed(() =>
|
||||||
_.chain(searchResult.value).groupBy('category').mapValues(categoryOptions => _.take(categoryOptions, 5)).value());
|
_.chain(searchResult.value)
|
||||||
|
.groupBy('category')
|
||||||
|
.mapValues(categoryOptions => _.take(categoryOptions, 5))
|
||||||
|
.value(),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filteredSearchResult,
|
filteredSearchResult,
|
||||||
|
|
|
@ -9,6 +9,8 @@ const inputRef = ref();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isMac = computed(() => window.navigator.userAgent.toLowerCase().includes('mac'));
|
const isMac = computed(() => window.navigator.userAgent.toLowerCase().includes('mac'));
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const commandPaletteStore = useCommandPaletteStore();
|
const commandPaletteStore = useCommandPaletteStore();
|
||||||
const { searchPrompt, filteredSearchResult } = storeToRefs(commandPaletteStore);
|
const { searchPrompt, filteredSearchResult } = storeToRefs(commandPaletteStore);
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ function activateOption(option: PaletteOption) {
|
||||||
<span flex items-center gap-3 op-40>
|
<span flex items-center gap-3 op-40>
|
||||||
|
|
||||||
<icon-mdi-search />
|
<icon-mdi-search />
|
||||||
Search...
|
{{ t('home.commandPalette.search') }}
|
||||||
|
|
||||||
<span hidden flex-1 border border-current border-op-40 rounded border-solid px-5px py-3px sm:inline>
|
<span hidden flex-1 border border-current border-op-40 rounded border-solid px-5px py-3px sm:inline>
|
||||||
{{ isMac ? 'Cmd' : 'Ctrl' }} + K
|
{{ isMac ? 'Cmd' : 'Ctrl' }} + K
|
||||||
|
@ -108,11 +110,11 @@ function activateOption(option: PaletteOption) {
|
||||||
</c-button>
|
</c-button>
|
||||||
|
|
||||||
<c-modal v-model:open="isModalOpen" class="palette-modal" shadow-xl important:max-w-650px important:pa-12px @keydown="handleKeydown">
|
<c-modal v-model:open="isModalOpen" class="palette-modal" shadow-xl important:max-w-650px important:pa-12px @keydown="handleKeydown">
|
||||||
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text :placeholder="t('home.commandPalette.placeholder')" autofocus clearable />
|
||||||
|
|
||||||
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
||||||
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
|
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
|
||||||
{{ category }}
|
{{ t(category) }}
|
||||||
</div>
|
</div>
|
||||||
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,6 +8,7 @@ const emit = defineEmits(['activated']);
|
||||||
const { option } = toRefs(props);
|
const { option } = toRefs(props);
|
||||||
|
|
||||||
const { selected } = toRefs(props);
|
const { selected } = toRefs(props);
|
||||||
|
const { t } = useI18n();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -29,7 +30,7 @@ const { selected } = toRefs(props);
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="option.description" truncate lh-tight op-60>
|
<div v-if="option.description" truncate lh-tight op-60>
|
||||||
{{ option.description }}
|
{{ t(option.description) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,9 +7,9 @@ import { useToolStore } from '@/tools/tools.store';
|
||||||
import { config } from '@/config';
|
import { config } from '@/config';
|
||||||
|
|
||||||
const toolStore = useToolStore();
|
const toolStore = useToolStore();
|
||||||
|
|
||||||
useHead({ title: 'IT Tools - Handy online tools for developers' });
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const title = t('home.categories.title');
|
||||||
|
useHead({ title });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -17,29 +17,28 @@ const { t } = useI18n();
|
||||||
<div class="grid-wrapper">
|
<div class="grid-wrapper">
|
||||||
<n-grid v-if="config.showBanner" x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
<n-grid v-if="config.showBanner" x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<ColoredCard title="You like it-tools?" :icon="Heart">
|
<ColoredCard title="{{t('home.categories.youLikeItTools')}}" :icon="Heart">
|
||||||
Give us a star on
|
t('home.categories.giveUsAStarOn')
|
||||||
<a
|
<a
|
||||||
href="https://github.com/CorentinTh/it-tools"
|
href="https://github.com/CorentinTh/it-tools"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="IT-Tools' GitHub repository"
|
aria-label="IT-Tools' GitHub repository"
|
||||||
>GitHub</a>
|
>GitHub</a>
|
||||||
or follow us on
|
t('home.categories.orFollowUsOn')
|
||||||
<a
|
<a
|
||||||
href="https://twitter.com/ittoolsdottech"
|
href="https://twitter.com/ittoolsdottech"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="IT-Tools' Twitter account"
|
aria-label="IT-Tools' Twitter account"
|
||||||
>Twitter</a>! Thank you
|
>Twitter</a>t('home.categories.thanks')
|
||||||
<n-icon :component="Heart" />
|
<n-icon :component="Heart" />
|
||||||
</ColoredCard>
|
</ColoredCard>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
|
|
||||||
<transition name="height">
|
<transition name="height">
|
||||||
<div v-if="toolStore.favoriteTools.length > 0">
|
<div v-if="toolStore.favoriteTools.length > 0">
|
||||||
<n-h3>Your favorite tools</n-h3>
|
<n-h3>{{ t('home.categories.favoriteTools') }}</n-h3>
|
||||||
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
||||||
<n-gi v-for="tool in toolStore.favoriteTools" :key="tool.name">
|
<n-gi v-for="tool in toolStore.favoriteTools" :key="tool.name">
|
||||||
<ToolCard :tool="tool" />
|
<ToolCard :tool="tool" />
|
||||||
|
@ -57,7 +56,7 @@ const { t } = useI18n();
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-h3>All the tools</n-h3>
|
<n-h3>{{ t('home.categories.allTools') }}</n-h3>
|
||||||
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
||||||
<n-gi v-for="tool in toolStore.tools" :key="tool.name">
|
<n-gi v-for="tool in toolStore.tools" :key="tool.name">
|
||||||
<transition>
|
<transition>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { defineTool } from '../tool';
|
||||||
export const tool = defineTool({
|
export const tool = defineTool({
|
||||||
name: 'XML formatter',
|
name: 'XML formatter',
|
||||||
path: '/xml-formatter',
|
path: '/xml-formatter',
|
||||||
description: 'Prettify your XML string to a human friendly readable format.',
|
description: 'tools.xmlFormat.description',
|
||||||
keywords: ['xml', 'prettify', 'format'],
|
keywords: ['xml', 'prettify', 'format'],
|
||||||
component: () => import('./xml-formatter.vue'),
|
component: () => import('./xml-formatter.vue'),
|
||||||
icon: Code,
|
icon: Code,
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import { formatXml, isValidXML } from './xml-formatter.service';
|
import { formatXml, isValidXML } from './xml-formatter.service';
|
||||||
import type { UseValidationRule } from '@/composable/validation';
|
import type { UseValidationRule } from '@/composable/validation';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const defaultValue = '<hello><world>foo</world><world>bar</world></hello>';
|
const defaultValue = '<hello><world>foo</world><world>bar</world></hello>';
|
||||||
const indentSize = useStorage('xml-formatter:indent-size', 2);
|
const indentSize = useStorage('xml-formatter:indent-size', 2);
|
||||||
const collapseContent = useStorage('xml-formatter:collapse-content', true);
|
const collapseContent = useStorage('xml-formatter:collapse-content', true);
|
||||||
|
@ -17,7 +19,7 @@ function transformer(value: string) {
|
||||||
const rules: UseValidationRule<string>[] = [
|
const rules: UseValidationRule<string>[] = [
|
||||||
{
|
{
|
||||||
validator: isValidXML,
|
validator: isValidXML,
|
||||||
message: 'Provided XML is not valid.',
|
message: t('tools.xmlFormat.providedXmlIsNotValid'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
@ -25,19 +27,19 @@ const rules: UseValidationRule<string>[] = [
|
||||||
<template>
|
<template>
|
||||||
<div important:flex-full important:flex-shrink-0 important:flex-grow-0>
|
<div important:flex-full important:flex-shrink-0 important:flex-grow-0>
|
||||||
<div flex justify-center>
|
<div flex justify-center>
|
||||||
<n-form-item label="Collapse content:" label-placement="left">
|
<n-form-item :label="t('tools.xmlFormat.collapseContent')" label-placement="left">
|
||||||
<n-switch v-model:value="collapseContent" />
|
<n-switch v-model:value="collapseContent" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="Indent size:" label-placement="left" label-width="100" :show-feedback="false">
|
<n-form-item :label="t('tools.xmlFormat.indentSize')" label-placement="left" label-width="100" :show-feedback="false">
|
||||||
<n-input-number v-model:value="indentSize" min="0" max="10" w-100px />
|
<n-input-number v-model:value="indentSize" min="0" max="10" w-100px />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<format-transformer
|
<format-transformer
|
||||||
input-label="Your XML"
|
:input-label="t('tools.xmlFormat.inputLabel')"
|
||||||
input-placeholder="Paste your XML here..."
|
:input-placeholder="t('tools.xmlFormat.placeHolder')"
|
||||||
output-label="Formatted XML from your XML"
|
:output-label="t('tools.xmlFormat.outputLabel')"
|
||||||
output-language="xml"
|
output-language="xml"
|
||||||
:input-validation-rules="rules"
|
:input-validation-rules="rules"
|
||||||
:transformer="transformer"
|
:transformer="transformer"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue