Add localization

pull/1/head
Made Baruna 2021-03-14 18:23:17 +08:00
parent 7fe43283ac
commit 604d600d06
33 changed files with 1156 additions and 277 deletions

View File

@ -23,6 +23,7 @@
"@rollup/plugin-babel": "^5.0.0",
"@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-dynamic-import-vars": "^1.1.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^10.0.0",
"@rollup/plugin-replace": "^2.3.4",
"@rollup/plugin-url": "^5.0.0",
@ -37,6 +38,7 @@
"rollup-plugin-terser": "^7.0.0",
"sapper": "^0.28.0",
"svelte": "^3.17.3",
"svelte-i18n": "^3.3.6",
"svelte-preprocess": "^4.5.1",
"svelte-simple-modal": "^0.6.1",
"tailwindcss": "^1.9.5"

View File

@ -6,6 +6,8 @@ import url from '@rollup/plugin-url';
import svelte from 'rollup-plugin-svelte';
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';
import json from '@rollup/plugin-json';
import config from 'sapper/config/rollup.js';
import { config as envConfig } from 'dotenv';
@ -50,6 +52,7 @@ export default {
dedupe: ['svelte'],
}),
commonjs(),
json(),
legacy &&
babel({
@ -113,6 +116,7 @@ export default {
dedupe: ['svelte'],
}),
commonjs(),
json(),
],
external: Object.keys(pkg.dependencies).concat(require('module').builtinModules),

View File

@ -1,5 +1,9 @@
import * as sapper from '@sapper/app';
import { startClient } from './i18n.js';
startClient();
sapper.start({
target: document.querySelector('#sapper')
});

View File

@ -1,5 +1,6 @@
<script>
import { mdiHelpCircle, mdiInformation, mdiInformationVariant } from '@mdi/js';
import { t } from 'svelte-i18n';
import { mdiInformation } from '@mdi/js';
import Icon from './Icon.svelte';
</script>
@ -8,16 +9,16 @@
<span class="text-gray-400 mr-2">
<Icon path={mdiInformation} />
</span>
<h1 class="font-display text-gray-400 text-lg">Click the picture to maximize</h1>
<h1 class="font-display text-gray-400 text-lg">{$t('calculator.guide.clickToMaximize')}</h1>
</div>
<div class="bg-background p-2 rounded-xl mb-2">
<h1 class="font-display text-white text-lg">How to use the Weapon Calculator</h1>
<h1 class="font-display text-white text-lg">{$t('calculator.guide.howToWeapon')}</h1>
<a href="/images/calculator-weapon.png" target="__blank">
<img src="/images/calculator-weapon.png" alt="how to use calculator weapon" />
</a>
</div>
<div class="bg-background p-2 rounded-xl mb-2 mt-8">
<h1 class="font-display text-white text-lg">How to use the Character Calculator</h1>
<h1 class="font-display text-white text-lg">{$t('calculator.guide.howToCharacter')}</h1>
<a href="/images/calculator-character.png" target="__blank">
<img src="/images/calculator-character.png" alt="how to use calculator character" />
</a>

View File

@ -9,6 +9,15 @@
}
</script>
<div class="flex items-center lg:hidden fixed w-full h-16 header bg-background z-30 shadow-md overflow-hidden">
<a href="/" class="flex-1 pl-4 md:pl-8 font-display text-3xl font-black text-white relative z-10 pt-2">
Paimon<span class="text-xl text-primary">.moe</span>
</a>
<div class="p-8 cursor-pointer" on:click={showMenu}>
<Icon path={mdiMenu} color="white" />
</div>
</div>
<style>
.header::after {
content: '';
@ -22,12 +31,3 @@
background-size: contain;
}
</style>
<a href="/" class="flex items-center lg:hidden fixed w-full h-16 header bg-background z-30 shadow-md overflow-hidden">
<h1 class="flex-1 pl-4 md:pl-8 font-display text-3xl font-black text-white relative z-10 pt-2">
Paimon<span class="text-xl text-primary">.moe</span>
</h1>
<div class="p-8 cursor-pointer" on:click={showMenu}>
<Icon path={mdiMenu} color="white" />
</div>
</a>

View File

@ -2,6 +2,7 @@
import { fly } from 'svelte/transition';
import { getContext } from 'svelte';
import { mdiCloseCircle } from '@mdi/js';
import { locale } from 'svelte-i18n';
import Icon from '../Icon.svelte';
@ -18,6 +19,13 @@
export let segment;
export let mobile = false;
const languages = [
{ id: 'en', label: 'English' },
{ id: 'id', label: 'Indonesia' },
];
$: locales = languages.filter((e) => e.id !== $locale.substring(0, 2));
$: currentLocale = languages.find((e) => e.id === $locale.substring(0, 2));
function openDonationModal() {
openModal(
DonateModal,
@ -32,6 +40,10 @@
function close() {
showSidebar.set(false);
}
function changeLocale(lang) {
locale.set(lang);
}
</script>
<div
@ -92,6 +104,25 @@
href="/settings"
/>
<div class="mt-8 md:mt-0 md:flex-1" />
<div
class="locale-selector flex items-center justify-center my-4 py-2 cursor-pointer
rounded-xl hover:bg-black hover:bg-opacity-50 relative w-40"
>
<img class="w-4 h-4 rounded-full mr-2" alt={currentLocale.label} src="/images/locales/{currentLocale.id}.svg" />
<span class="text-gray-400">{currentLocale.label}</span>
<div class="locale-dropdown" style="top: {locales.length * -45}px;">
{#each locales as locale}
<div
class="flex items-center justify-center py-2 cursor-pointer rounded-xl bg-opacity-50 bg-black hover:bg-opacity-75"
on:click={() => changeLocale(locale.id)}
>
<img class="w-4 h-4 rounded-full mr-2" alt={locale.label} src="/images/locales/{locale.id}.svg" />
<span class="text-gray-400">{locale.label}</span>
</div>
<div class="w-40" style="height: 5px;" />
{/each}
</div>
</div>
<Button on:click={openDonationModal}>
<img class="inline w-8 h-8" src="/images/mora.png" alt="donate" />
Donate
@ -110,4 +141,16 @@
background: url('/paimon_bg.png') no-repeat top;
background-size: contain;
}
.locale-dropdown {
@apply hidden;
@apply absolute;
@apply w-40;
}
.locale-selector:hover {
.locale-dropdown {
@apply block;
}
}
</style>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { mdiClose } from '@mdi/js';
import { itemGroup } from '../data/itemGroup';
@ -28,7 +30,7 @@
<div>
{#if withRarity}
<p class="text-white font-bold mb-4 text-lg">Select Rarity</p>
<p class="text-white font-bold mb-4 text-lg">{$t('items.add.rarity')}</p>
<div class="flex items-center mb-4 text-white">
{#each itemGroup[item].items as item, index}
<div
@ -54,9 +56,9 @@
</div>
</div>
{/if}
<p class="text-white font-bold mb-4 text-lg">Amount</p>
<p class="text-white font-bold mb-4 text-lg">{$t('items.add.amount')}</p>
<div class="inline-flex mb-4">
<Input className="mr-2" type="number" min={1} bind:value={amount} placeholder="Input amount..." />
<Input className="mr-2" type="number" min={1} bind:value={amount} placeholder={$t('items.add.inputAmount')} />
<Button className="mr-2 w-16" on:click={() => addAmount(1)}>+1</Button>
<Button className="w-16" on:click={() => addAmount(10)}>+10</Button>
</div>
@ -75,7 +77,7 @@
<span>{amount}</span>
</div>
<div class="flex justify-end gap-2">
<Button on:click={cancel}>Cancel</Button>
<Button on:click={() => addTodo(selectedItem, amount)} color="green">Add to Todo</Button>
<Button on:click={cancel}>{$t('items.add.cancel')}</Button>
<Button on:click={() => addTodo(selectedItem, amount)} color="green">{$t('items.add.add')}</Button>
</div>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t} from "svelte-i18n";
import Button from './Button.svelte';
export let todo;
@ -7,7 +9,7 @@
</script>
<div>
<p class="text-white font-bold mb-4 text-lg">Delete this todo?</p>
<p class="text-white font-bold mb-4 text-lg">{$t('todo.delete.title')}</p>
<div class="flex items-center mb-4 text-white">
{#if todo.type === 'weapon'}
<img
@ -30,7 +32,7 @@
{/if}
</div>
<div class="flex justify-end gap-2">
<Button on:click={cancel}>Cancel</Button>
<Button on:click={deleteTodo} color="red">Delete</Button>
<Button on:click={cancel}>{$t('todo.delete.cancel')}</Button>
<Button on:click={deleteTodo} color="red">{$t('todo.delete.delete')}</Button>
</div>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { mdiPencil, mdiStar } from '@mdi/js';
import Icon from './Icon.svelte';
import Checkbox from '../components/Checkbox.svelte';
@ -16,61 +18,59 @@
</script>
<div>
<h1 class="font-display text-white text-xl mb-4">Wish Counter Help & Settings</h1>
<h1 class="font-display text-white text-xl mb-4">{$t('wish.help.title')}</h1>
<div class="text-white p-2 bg-background rounded-xl">
<div class="py-2 pl-4">
<Checkbox disabled={false} bind:checked={enableManual}
><span class="select-none cursor-pointer">Enable Manual Input</span></Checkbox
><span class="select-none cursor-pointer">{$t('wish.help.enableManual')}</span></Checkbox
>
</div>
<p class="text-red-300">
Using the Auto Import and manual input together is not recommended, still need some testing!
</p>
<p>Consider using the Auto Import first, access it on button beside the button you click to open this How To</p>
<p class="text-red-300">{$t('wish.help.notice')}</p>
<p>{$t('wish.help.consider')}</p>
</div>
<h1 class="font-display text-white text-xl mt-6 mb-4">How to use manual input</h1>
<h1 class="font-display text-white text-xl mt-6 mb-4">{$t('wish.help.howto.title')}</h1>
<div class="text-white p-2 bg-background rounded-xl mt-4">
<p class="mb-2">After you do 1 pull Wish:</p>
<p class="mb-2">{$t('wish.help.howto.subtitle')}</p>
<p class="mb-2">
Press
{$t('wish.help.howto.press')}
<b class="rounded-lg px-2 py-1 border-white border inline-flex items-center">+1</b>
when you get
{$t('wish.help.howto.whenYouGet')}
<span class="inline-flex items-center"
>3
<Icon path={mdiStar} size={0.7} /></span
>
</p>
<p class="mb-2">
Press
{$t('wish.help.howto.press')}
<b class="rounded-lg px-2 py-1 border-white border inline-flex items-center"
>Get 4
<Icon path={mdiStar} size={0.7} /></b
>
when you get
{$t('wish.help.howto.whenYouGet')}
<span class="inline-flex items-center"
>4
<Icon path={mdiStar} size={0.7} /></span
>
</p>
<p class="mb-2">
Press
{$t('wish.help.howto.press')}
<b class="rounded-lg px-2 py-1 border-white border inline-flex items-center"
>Get 5
<Icon path={mdiStar} size={0.7} /></b
>
when you get
{$t('wish.help.howto.whenYouGet')}
<span class="inline-flex items-center"
>5
<Icon path={mdiStar} size={0.7} /></span
>
</p>
<p class="text-gray-400">
It will automatically add the lifetime pulls,
{$t('wish.help.howto.p1')}
<span class="inline-flex items-center"
>5
<Icon path={mdiStar} size={0.7} /></span
>
and
{$t('wish.help.howto.and')}
<span class="inline-flex items-center"
>4
<Icon path={mdiStar} size={0.7} /></span
@ -78,48 +78,43 @@
pity
</p>
<p class="text-gray-400">
When the
{$t('wish.help.howto.p2.0')}
<span class="inline-flex items-center"
>4
<Icon path={mdiStar} size={0.7} /></span
>
pity reach 10, it will automatically reset to 0
{$t('wish.help.howto.p2.1')}
</p>
<p class="text-gray-400">
When the
{$t('wish.help.howto.p3.0')}
<span class="inline-flex items-center"
>5
<Icon path={mdiStar} size={0.7} /></span
>
pity reach 90, it will automatically reset to 0
{$t('wish.help.howto.p3.1')}
</p>
</div>
<div class="text-white p-2 bg-background rounded-xl mt-4">
For when you do 10 pulls Wish, press the
{$t('wish.help.howto.p4.0')}
<b class="rounded-lg px-2 py-1 border-white border inline-flex items-center">+10</b>
button,
<span class="text-gray-400"
>but the pity counter won't be accurate, because there is no way to tell when the drop occur (maybe you got it on
the 1st or even the 10th). To make the counter still accurate, you need to check it on the history table and add
it 1 by 1 like you do 1 pull Wish.</span
>
<span class="text-gray-400">{$t('wish.help.howto.p4.1')}</span>
</div>
<div class="text-white p-2 bg-background rounded-xl mt-4">
You can also press
{$t('wish.help.howto.p5.0')}
<b class="rounded-lg px-2 py-1 border-white border inline-flex items-center"><Icon path={mdiPencil} size={0.7} /></b
>
button to edit the values manually!
{$t('wish.help.howto.p5.1')}
</div>
<div class="text-white p-2 bg-background rounded-xl mt-4">
Press the arrow on the bottom to see your pulls detail. A popup will show when you get <span
class="inline-flex items-center"
{$t('wish.help.howto.p6.0')}
<span class="inline-flex items-center"
>5
<Icon path={mdiStar} size={0.7} /></span
>
or
{$t('wish.help.howto.p6.1')}
<span class="inline-flex items-center"
>4
<Icon path={mdiStar} size={0.7} /></span
>. Or you can add or edit the table manually.
>. {$t('wish.help.howto.p6.2')}
</div>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { mdiClose, mdiDownload, mdiHelpCircle, mdiInformation, mdiLoading } from '@mdi/js';
import { onMount } from 'svelte';
import dayjs from 'dayjs';
@ -102,7 +104,7 @@
url = new URL(genshinLink);
}
} catch (err) {
pushToast('Invalid link, please check it again', 'error');
pushToast($t('wish.import.invalidLink'), 'error');
}
try {
@ -178,7 +180,7 @@
if (!res.ok) {
processingLog = false;
pushToast('Error code returned from MiHoYo API, please try again later!', 'error');
pushToast($t('wish.import.errorApi'), 'error');
throw 'error code';
}
@ -192,7 +194,7 @@
throw 'error code';
}
pushToast('Error code returned from MiHoYo API, please try again later!', 'error');
pushToast($t('wish.import.errorApi'), 'error');
throw 'error code';
}
@ -200,7 +202,7 @@
result = dat.data.list;
} catch (err) {
processingLog = false;
pushToast('Connection timeout, please wait a moment and try again later', 'error');
pushToast($t('wish.import.timeout'), 'error');
throw 'network error';
}
@ -244,7 +246,7 @@
console.log(wishes);
} catch (err) {
processingLog = false;
pushToast('Invalid data returned from API, try again later!', 'error');
pushToast($t('wish.import.invalidData'), 'error');
throw 'invalid data';
}
} while (result.length > 0 && lastTime > newestPullTime);
@ -372,7 +374,7 @@
}
calculatingPity = false;
pushToast('Import success 😀!');
pushToast($t('wish.import.success'));
closeModal();
}
@ -446,7 +448,7 @@
</script>
{#if processingLog}
<h1 class="font-display text-white text-xl mb-2">Import Wish History</h1>
<h1 class="font-display text-white text-xl mb-2">{$t('wish.import.title')}</h1>
<div class="bg-background rounded-xl px-4 py-2 text-white mt-2">
{#if finishedProcessingLog}
<table class="min-w-full md:min-w-0">
@ -462,24 +464,24 @@
{numberFormat.format(wishes[code].length)}
</span>
{:else}
<span class="text-white">No New Wishes</span>
<span class="text-white">{$t('wish.import.nonew')}</span>
{/if}
</td>
</tr>
{/each}
</table>
<p class="mt-4">Imported wishes will be appended or replaced accordingly to existing data</p>
<p>If you don't have any data saved before, first wish will be counted as pity 1</p>
<p class="font-semibold">Save the data?</p>
<p class="mt-4">{$t('wish.import.importNotice1')}</p>
<p>{$t('wish.import.importNotice2')}</p>
<p class="font-semibold">{$t('wish.import.saveData')}</p>
{:else if calculatingPity}
<Icon path={mdiLoading} spin color="white" />
Re-calculating pity...
{$t('wish.import.reCalculating')}
{:else if fetchingWishes}
<div class="flex">
<Icon path={mdiLoading} spin color="white" />
<div class="ml-2">
<p>{`Processing ${currentBanner} Banner`}</p>
<p>{`Page ${currentPage}`}</p>
<p>{$t('wish.import.processing')} {currentBanner} {$t('wish.import.banner')}</p>
<p>{$t('wish.import.page')} {currentPage}</p>
</div>
</div>
<table class="min-w-full md:min-w-0 mt-2">
@ -501,79 +503,65 @@
</table>
{:else}
<Icon path={mdiLoading} spin color="white" />
Parsing...
{$t('wish.import.parsing')}
{/if}
</div>
<div class="flex justify-end mt-4">
{#if finishedProcessingLog && !calculatingPity}
<Button on:click={saveData} color="green" className="mr-4">Save</Button>
<Button on:click={saveData} color="green" className="mr-4">{$t('wish.import.save')}</Button>
{/if}
<Button on:click={cancel} disabled={calculatingPity || cancelled}>{cancelled ? 'Cancelling...' : 'Cancel'}</Button>
<Button on:click={cancel} disabled={calculatingPity || cancelled}
>{cancelled ? $t('wish.import.cancelling') : $t('wish.import.cancel')}</Button
>
</div>
{:else}
<div>
{#if showFaq}
<h1 class="font-display text-white text-xl mb-2">Import Wish History FAQS</h1>
<h1 class="font-display text-white text-xl mb-2">{$t('wish.import.faqs.title')}</h1>
<div class="font-body">
<p class="text-white font-semibold">How does it work?</p>
<p class="text-white font-semibold">{$t('wish.import.faqs.q1')}</p>
<p class="text-gray-400">{$t('wish.import.faqs.a1')}</p>
<p class="text-white font-semibold mt-4">{$t('wish.import.faqs.q2')}</p>
<p class="text-gray-400">{$t('wish.import.faqs.a2')}</p>
<p class="text-white font-semibold mt-4">{$t('wish.import.faqs.q3')}</p>
<p class="text-gray-400">
Genshin Impact wish history is basically a web page, so you can access it by opening the web page url. A
temporary key will be generated after you open the wish history page or the feedback page, and the importer
will automatically use the MiHoYo API to fetch your wish history.
</p>
<p class="text-white font-semibold mt-4">Is it safe? Will I get banned?</p>
<p class="text-gray-400">
Paimon.moe use the same request that Genshin Impact use to get the wish history, and Paimon.moe has no way
whatsoever to modify any game files or memory, and it should be safe. But use it at your own risk (well I use
it on my main account). You still can input your data manually 😀.
</p>
<p class="text-white font-semibold mt-4">Can you hack my account then?</p>
<p class="text-gray-400">
Paimon.moe never save anything related to your account (even your uid or nickname), so the answer is no. This
project is open source on
{$t('wish.import.faqs.a3.0')}
<a class="text-primary hover:underline" target="__blank" href="https://github.com/MadeBaruna/paimon-moe"
>Github</a
>, I'm not planning to damage my reputation by hacking other people account.
</p>
<p class="text-white font-semibold mt-4">
Hey I checked the request and stuff, but why it request to your domain instead of MiHoYo API?
>{$t('wish.import.faqs.a3.1')}
</p>
<p class="text-white font-semibold mt-4">{$t('wish.import.faqs.q4')}</p>
<p class="text-gray-400">
Paimon.moe cannot request directly to MiHoYo API because of
{$t('wish.import.faqs.a4.0')}
<a
class="text-primary hover:underline"
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"
target="__blank"
>
CORS</a
>, so the request redirected to a simple cors proxy to make it work. You can see the code
>{$t('wish.import.faqs.a4.1')}
<a
class="text-primary hover:underline"
href="https://gist.github.com/MadeBaruna/64785ae992c924e0cbfe575e404b7155"
target="__blank">here</a
target="__blank">{$t('wish.import.faqs.a4.2')}</a
>
</p>
<p class="text-white font-semibold mt-4">Do you store my temporary key or wish history?</p>
<p class="text-white font-semibold mt-4">{$t('wish.import.faqs.q5')}</p>
<p class="text-gray-400">
Paimon.moe never store your key, and use HTTPS to pass your url to a cors proxy to make the CORS works. All
your wish history is saved on your device only (or your google drive if you turn on sync on setting).
Paimon.moe does not save anything to the server (yes anything).
{$t('wish.import.faqs.a5')}
<!-- If you don't want any passing around your url, you can use the small importer app to process the wish
history on your local PC (PC Local option) -->
</p>
<p class="text-white font-semibold mt-4">I tried the step, but I got some API error?</p>
<p class="text-gray-400">
Make sure you copy all the text (just hold and press select all), maybe you left over some text that are
needed for the importer to work
</p>
<p class="text-white font-semibold mt-4">{$t('wish.import.faqs.q6')}</p>
<p class="text-gray-400">{$t('wish.import.faqs.a6')}</p>
</div>
{:else}
<div class="flex flex-col md:flex-row items-start md:items-center">
<h1 class="font-display text-white text-xl mb-2 mr-2">Import Wish History</h1>
<h1 class="font-display text-white text-xl mb-2 mr-2">{$t('wish.import.title')}</h1>
<Button size="sm" on:click={() => toggleFaqs(true)}>
<Icon path={mdiHelpCircle} color="white" />
FAQS - READ FIRST
{$t('wish.import.faqsButton')}
</Button>
</div>
<div class="flex mt-4 flex-wrap">
@ -599,13 +587,13 @@
{#if selectedType === 'pc'}
<div class="bg-background rounded-xl px-4 py-2 text-white mb-4 mt-2">
<ol class="list-decimal ml-4">
<li class="my-2">Open Paimon menu [ESC]</li>
<li class="my-2">Click Feedback</li>
<li class="my-2">Wait for it to load and a browser page should open</li>
<li class="my-2">Copy & paste the link to the textbox below</li>
<li class="my-2">{$t('wish.import.guide.pc.0')}</li>
<li class="my-2">{$t('wish.import.guide.pc.1')}</li>
<li class="my-2">{$t('wish.import.guide.pc.2')}</li>
<li class="my-2">{$t('wish.import.guide.pc.3')}</li>
</ol>
</div>
<Input bind:value={genshinLink} placeholder="Paste link here... https://webstatic..." />
<Input bind:value={genshinLink} placeholder={$t('wish.import.guide.pc.4')} />
{:else if selectedType === 'pclocal'}
<div class="bg-background rounded-xl px-4 py-2 text-white mb-4 mt-2">
<ol class="list-decimal ml-4">
@ -624,33 +612,31 @@
{:else if selectedType === 'android'}
<div class="bg-background rounded-xl px-4 py-2 text-white mb-4 mt-2">
<ol class="list-decimal ml-4">
<li class="my-2">Open Paimon menu</li>
<li class="my-2">Press Feedback</li>
<li class="my-2">Wait for it to load and a feedback page should open</li>
<li class="my-2">Turn off your wifi and data connection</li>
<li class="my-2">Press refresh on top right corner</li>
<li class="my-2">The page should error and show you a text with black font</li>
<li class="my-2">
Hold the text and press select all, then copy that text (don't copy only some portion of the text)
</li>
<li class="my-2">Turn on your wifi or data connection</li>
<li class="my-2">Paste the text to the textbox below</li>
<li class="my-2">{$t('wish.import.guide.android.0')}</li>
<li class="my-2">{$t('wish.import.guide.android.1')}</li>
<li class="my-2">{$t('wish.import.guide.android.2')}</li>
<li class="my-2">{$t('wish.import.guide.android.3')}</li>
<li class="my-2">{$t('wish.import.guide.android.4')}</li>
<li class="my-2">{$t('wish.import.guide.android.5')}</li>
<li class="my-2">{$t('wish.import.guide.android.6')}</li>
<li class="my-2">{$t('wish.import.guide.android.7')}</li>
<li class="my-2">{$t('wish.import.guide.android.8')}</li>
</ol>
</div>
<Input bind:value={genshinLink} placeholder="Paste text here... Webpage not available..." />
<Input bind:value={genshinLink} placeholder={$t('wish.import.guide.android.9')} />
{:else if selectedType === 'ios'}
<div class="bg-background rounded-xl px-4 py-2 text-white mb-4 mt-2">
<ol class="list-decimal ml-4">
<li class="my-2">Open Paimon menu</li>
<li class="my-2">Press Feedback</li>
<li class="my-2">Wait for it to load and a feedback page should open</li>
<li class="my-2">Press In-game issue</li>
<li class="my-2">Press Co-Op Mode</li>
<li class="my-2">There is a link on the bottom of the reply, press that</li>
<li class="my-2">A browser should open up, copy the link and paste it below</li>
<li class="my-2">{$t('wish.import.guide.ios.0')}</li>
<li class="my-2">{$t('wish.import.guide.ios.1')}</li>
<li class="my-2">{$t('wish.import.guide.ios.2')}</li>
<li class="my-2">{$t('wish.import.guide.ios.3')}</li>
<li class="my-2">{$t('wish.import.guide.ios.4')}</li>
<li class="my-2">{$t('wish.import.guide.ios.5')}</li>
<li class="my-2">{$t('wish.import.guide.ios.6')}</li>
</ol>
</div>
<Input bind:value={genshinLink} placeholder="Paste link here... https://genshin.mihoyo..." />
<Input bind:value={genshinLink} placeholder={$t('wish.import.guide.ios.7')} />
{/if}
{/if}
@ -658,19 +644,19 @@
{#if !showFaq}
<div class="flex-1 flex mb-4 md:mb-0 md:ml-4">
<Checkbox disabled={false} bind:checked={newOnly}>
<span class="text-white select-none"> Import new wish only </span>
<span class="text-white select-none">{$t('wish.import.importNewWishOnly')}</span>
</Checkbox>
<span class="tooltip ml-2">
<Icon path={mdiInformation} color="white" />
<span class="tooltip-content"> Uncheck only if you need to re-import all your wish history</span>
<span class="tooltip-content">{$t('wish.import.importNewWishUncheck')}</span>
</span>
</div>
{/if}
<div>
{#if !showFaq}
<Button on:click={startImport} color="green" className="mr-4">Import</Button>
<Button on:click={startImport} color="green" className="mr-4">{$t('wish.import.import')}</Button>
{/if}
<Button on:click={showFaq ? () => toggleFaqs(false) : () => closeModal()}>Close</Button>
<Button on:click={showFaq ? () => toggleFaqs(false) : () => closeModal()}>{$t('wish.import.close')}</Button>
</div>
</div>
</div>

63
src/i18n.js Normal file
View File

@ -0,0 +1,63 @@
import { addMessages, init, getLocaleFromNavigator, locale as $locale } from 'svelte-i18n';
import en from './locales/en.json';
import id from './locales/id.json';
const INIT_OPTIONS = {
fallbackLocale: 'en',
initialLocale: null,
};
let currentLocale = null;
$locale.subscribe((value) => {
if (value == null) return;
currentLocale = value;
if (typeof window !== 'undefined') {
localStorage.setItem('locale', value);
}
});
addMessages('en', en);
addMessages('en-US', en);
addMessages('id', id);
export function startClient() {
const savedLocale = localStorage.getItem('locale');
init({
...INIT_OPTIONS,
initialLocale: savedLocale !== null ? savedLocale : getLocaleFromNavigator(),
});
}
const DOCUMENT_REGEX = /(^([^.?#@]+)?([?#](.+)?)?|service-worker.*?\.html)$/;
export function i18nMiddleware() {
init(INIT_OPTIONS);
return (req, res, next) => {
const isDocument = DOCUMENT_REGEX.test(req.originalUrl);
if (!isDocument) {
next();
return;
}
let locale = 'en';
if (req.headers['accept-language']) {
const headerLang = req.headers['accept-language'].split(',')[0].trim();
if (headerLang.length > 1) {
locale = headerLang;
}
} else {
locale = INIT_OPTIONS.initialLocale || INIT_OPTIONS.fallbackLocale;
}
if (locale != null && locale !== currentLocale) {
$locale.set(locale);
}
next();
};
}

266
src/locales/en.json Normal file
View File

@ -0,0 +1,266 @@
{
"characters": {
"title": "Characters",
"subtitle": "Stat numbers are at level 80 Ascension 6. You can also click the table header to sort!",
"name": "Name",
"element": "Element",
"rarity": "Rarity",
"weapon": "Weapon",
"hp": "HP",
"atk": "ATK",
"def": "DEF"
},
"wish": {
"autoImport": "Auto Import",
"helpAndSetting": "Help & Setting",
"wishesWorth": "Wishes Worth",
"lifetimePulls": "Lifetime Pulls",
"guarantee": "Guaranteed at {pity}",
"name": "Name",
"time": "Time",
"pity": "Pity",
"welcome": "Welcome to Paimon.moe Wish Counter! The recommended usage is to import your wish history with the Auto Importer.",
"welcomeStart1": "To start, press the",
"welcomeStart2": "button up there",
"manual": "If you want to manually input the data, you can enable it here:",
"manualButton": "Enable Manual Input",
"import": {
"title": "Import Wish History",
"faqsButton": "FAQS - READ FIRST",
"nonew": "No New Wishes",
"importNotice1": "Imported wishes will be appended or replaced accordingly to existing data",
"importNotice2": "If you don't have any data saved before, first wish will be counted as pity 1",
"saveData": "Save the data?",
"reCalculating": "Re-calculating pity...",
"processing": "Processing",
"banner": "Banner",
"page": "Page",
"parsing": "Parsing...",
"save": "Save",
"cancel": "Cancel",
"canceling": "Canceling...",
"importNewWishOnly": "Import new wish only",
"importNewWishUncheck": "Uncheck only if you need to re-import all your wish history",
"import": "Import",
"close": "Close",
"invalidLink": "Invalid link, please check it again",
"errorApi": "Error code returned from MiHoYo API, please try again later!",
"timeout": "Connection timeout, please wait a moment and try again later",
"invalidData": "Invalid data returned from API, try again later",
"success": "Import success 😀!",
"faqs": {
"title": "Import Wish History FAQS",
"q1": "How does it work?",
"a1": "Genshin Impact wish history is basically a web page, so you can access it by opening the web page url. A temporary key will be generated after you open the wish history page or the feedback page, and the importer will automatically use the MiHoYo API to fetch your wish history.",
"q2": "Is it safe? Will I get banned?",
"a2": "Paimon.moe use the same request that Genshin Impact use to get the wish history, and Paimon.moe has no way whatsoever to modify any game files or memory, and it should be safe. But use it at your own risk (well I use it on my main account). You still can input your data manually 😀.",
"q3": "Can you hack my account then?",
"a3": [
"Paimon.moe never save anything related to your account (even your uid or nickname), so the answer is no. This project is open source on",
", I'm not planning to damage my reputation by hacking other people account."
],
"q4": "Hey I checked the request and stuff, but why it request to your domain instead of MiHoYo API?",
"a4": [
"Paimon.moe cannot request directly to MiHoYo API because of",
", so the request redirected to a simple cors proxy to make it work. You can see the code",
"here"
],
"q5": "Do you store my temporary key or wish history?",
"a5": "Paimon.moe never store your key, and use HTTPS to pass your url to a cors proxy to make the CORS works. All your wish history is saved on your device only (or your google drive if you turn on sync on setting). Paimon.moe does not save anything to the server (yes anything).",
"q6": "I tried the step, but I got some API error?",
"a6": "Make sure you copy all the text (just hold and press select all), maybe you left over some text that are needed for the importer to work"
},
"guide": {
"pc": [
"Open Paimon menu [ESC]",
"Click Feedback",
"Wait for it to load and a browser page should open",
"Copy & paste the link to the textbox below",
"Paste link here... https://webstatic..."
],
"android": [
"Open Paimon menu",
"Press Feedback",
"Wait for it to load and a feedback page should open",
"Turn off your wifi and data connection",
"Press refresh on top right corner",
"The page should error and show you a text with black font",
"Hold the text and press select all, then copy that text (don't copy only some portion of the text)",
"Turn on your wifi or data connection",
"Paste the text to the textbox below",
"Paste text here... Webpage not available..."
],
"ios": [
"Open Paimon menu",
"Press Feedback",
"Wait for it to load and a feedback page should open",
"Press In-game issue",
"Press Co-Op Mode",
"There is a link on the bottom of the reply, press that",
"A browser should open up, copy the link and paste it below",
"Paste link here... https://genshin.mihoyo..."
]
}
},
"help": {
"title": "Wish Counter Help & Settings",
"enableManual": "Enable Manual Input",
"notice": "Using the Auto Import and manual input together is not recommended still need some testing!",
"consider": "Consider using the Auto Import first, access it on button beside the button you click to open this How To",
"howto": {
"title": "How to use manual input",
"subtitle": "After you do x1 pull Wish:",
"press": "Press",
"whenYouGet": "when you get",
"p1": "It will automatically add the lifetime pulls,",
"and": "and",
"p2": ["When the", "pity reach 10, it will automatically reset to 0"],
"p3": ["When the", "pity reach 90, it will automatically reset to 0"],
"p4": [
"For when you do x10 pulls Wish, press",
"but the pity counter won't be accurate, because there is no way to tell when the drop occur (maybe you got it on the 1st or even the 10th). To make the counter still accurate, you need to check it on the history table and add it 1 by 1 like you do 1 pull Wish."
],
"p5": ["You can also press", "button to edit the values manually!"],
"p6": [
"Press the arrow on the bottom to see your pulls detail. A popup will show up when you get",
"or",
"Or you can add or edit the table manually."
]
}
}
},
"calculator": {
"titleWeapon": "Weapon Calculator",
"titleCharacter": "Character Calculator",
"goto": "Go To",
"howToUse": "How to Use",
"guide": {
"clickToMaximize": "Click the picture to maximize",
"howToWeapon": "How to use the Weapon Calculator",
"howToCharacter": "How to use the Character Calculator"
},
"weapon": {
"calculateAscension": "Calculate Ascension Material?",
"selectRarity": "Select weapon rarity",
"selectWeapon": "Select weapon",
"current": "Current Weapon Level, Exp, & Ascension",
"inputCurrentLevel": "Input current weapon level...",
"inputCurrentExp": "Input current weapon exp...",
"intended": "Intended Weapon Level & Ascension",
"inputIntendedLevel": "Input intended weapon level...",
"resource": "Resource to Use",
"calculate": "Calculate",
"unknownInformation": "There are some unknown information",
"ascensionLevel": "Ascension level",
"mora": "Mora (approximate ±40)",
"expWasted": "EXP Wasted",
"addToTodo": "Add to Todo List",
"addedToTodo": "Added to Todo List"
},
"character": {
"calculateAscension": "Calculate Ascension Material?",
"selectCharacter": "Select character",
"current": "Current Character Level, Exp, & Ascension",
"inputCurrentLevel": "Input current character level...",
"inputCurrentExp": "Input current character exp...",
"intended": "Intended Character Level & Ascension",
"inputIntendedLevel": "Input intended character level...",
"resource": "Resource to Use",
"calculateTalent": "Calculate Talent Material?",
"inputTalentLevel": "Input the 1st, 2nd & 3rd current talent level",
"inputTalentNotice": "If it has different color, substract it by 3",
"inputTalent": ["1st talent lvl", "2nd talent lvl", "3rd talent lvl"],
"talentToLevel": "to level",
"calculate": "Calculate",
"unknownInformation": "There are some unknown information",
"ascensionLevel": "Ascension level",
"mora": "Mora (approximate ±40)",
"expWasted": "EXP Wasted",
"addToTodo": "Add to Todo List",
"addedToTodo": "Added to Todo List"
},
"expTable": {
"level": "Level",
"items": "Items",
"wasted": "Wasted Exp",
"mora": "Mora Cost"
}
},
"items": {
"title": "Item List",
"subtitle": "Click the item image to add it to the todo list",
"searchCharacter": "Search character",
"searchWeapon": "Search weapon",
"day": "Days",
"material": "Materials",
"characterWeapons": "Characters & Weapons",
"add": {
"rarity": "Select Rarity",
"amount": "Amount",
"inputAmount": "Input amount...",
"cancel": "Cancel",
"add": "Add to Todo"
}
},
"days": {
"Sunday": "Sunday",
"Monday": "Monday",
"Tuesday": "Tuesday",
"Wednesday": "Wednesday",
"Thursday": "Thursday",
"Friday": "Friday",
"Saturday": "Saturday"
},
"todo": {
"title": "Todo List",
"summary": "Summary",
"empty": ["Nothing to do yet 😀", "Add some from the Items page or the Calculator!"],
"farmableToday": "Farmable Today",
"resin": "resin needed",
"based": "Based on AR:{ar} and WL:{wl}",
"change": "(change on settings)",
"approximation": "Approximation calculated from drop rates by",
"delete": {
"title": "Delete this todo?",
"cancel": "Cancel",
"delete": "Delete"
}
},
"timeline": {
"title": "Timeline",
"localTime": "Show as local time",
"starting": "Starting in",
"ending": "Ending in",
"live": "Live Now!",
"finished": "Finished"
},
"settings": {
"version": "Data Version:",
"multiple": "Have multiple account? Choose account here to separate your wish and todo data",
"selectAccount": "Select your account",
"reset": "Reset",
"delete": "Delete",
"add": "Add",
"server": "Select your server:",
"drive": [
"Paimon.moe use Application Data Directory on your Google Drive to save and sync your wish counter and todo list.",
"Paimon.moe can only read and write file that this site create."
],
"driveError": "Google Drive API cannot be loaded",
"driveSignIn": "Sign in to Google Drive",
"driveSignOut": "Sign out Google Drive",
"synced": "Synced",
"waiting": "Waiting...",
"syncing": "Syncing...",
"lastSync": "Last Sync:",
"feedback": "If you found any bug, wrong data, or you have any feedback, please leave a message on",
"or": "or",
"thanks": "Thanks😁!",
"modal": {
"notice": "All todo and wish history data will be deleted",
"cancel": "Cancel",
"delete": "Delete",
"reset": "Reset"
}
}
}

266
src/locales/id.json Normal file
View File

@ -0,0 +1,266 @@
{
"characters": {
"title": "Karakter",
"subtitle": "Angka stat adalah saat level 80 Ascension 6. Kamu juga dapat mengklik judul tabel untuk mengurutkan!",
"name": "Nama",
"element": "Element",
"rarity": "Rarity",
"weapon": "Senjata",
"hp": "HP",
"atk": "ATK",
"def": "DEF"
},
"wish": {
"autoImport": "Import Otomatis",
"helpAndSetting": "Bantuan & Pengaturan",
"wishesWorth": "Wish Setara Dengan",
"lifetimePulls": "Total Pull",
"guarantee": "Dijamin saat {pity}",
"name": "Nama",
"time": "Waktu",
"pity": "Pity",
"welcome": "Selamat datang di Paimon.moe Wish Counter! Kamu bisa mengimport riwayat wish dengan menggunakan Import Otomatis.",
"welcomeStart1": "Tekan tombol",
"welcomeStart2": "diatas untuk mulai",
"manual": "Jika kamu ingin memasukkan datanya secara manual, kamu bisa menyalakannya disini:",
"manualButton": "Nyalakan Input Manual",
"import": {
"title": "Import Riwayat Wish",
"faqsButton": "FAQS - BACA DULU",
"nonew": "Tdk ada wish baru",
"importNotice1": "Wish yang diimport akan ditambah atau dihapus sesuai dengan data yang sudah pernah diimport",
"importNotice2": "Jika kamu tidak mempunyai data yang tersimpan sebelumnya, wish pertama akan dihitung sebagai pity 1.",
"saveData": "Simpan data?",
"reCalculating": "Menghitung pity...",
"processing": "Memproses",
"banner": "Banner",
"page": "Halaman",
"parsing": "Membaca...",
"save": "Simpan",
"cancel": "Batal",
"canceling": "Membatalkan...",
"importNewWishOnly": "Import wish baru saja",
"importNewWishUncheck": "Hapus centang hanya jika kamu ingin mengimport ulang semua riwayat history mu",
"import": "Import",
"close": "Tutup",
"invalidLink": "Link invalid, silahkan dicek kembali",
"errorApi": "Error code dikembalikan dari API MiHoYo, coba lagi nanti!",
"timeout": "Connection timeout, tunggu sebentar dan coba lagi nanti",
"invalidData": "Invalid data dikembalikan dari API, coba lagi nanti",
"success": "Import berhasil 😀!",
"faqs": {
"title": "Import Riwayat Wish FAQS",
"q1": "Cara kerjanya gimana?",
"a1": "Riwayat history Genshin Impact sebenarnya adalah sebuah halaman web, jadi kamu bisa mengakses nya dengan url web nya. Ada key sementara yang akan dibuat setelah kamu membuka halaman riwayat wish atau halaman feedback, dan importer ini akan secara otomatis menggunakan API MiHoYo untuk mengambil riwayat wish mu",
"q2": "Aman gak? Apakah bisa ke ban?",
"a2": "Paimon.moe menggunakan request yang sama yang digunakan di Genshin Impact untuk mengambil riwayat wish nya, dan Paimon.moe tidak bisa mengubah file-file game atau memori game, dan seharusnya aman. Namun gunakan dengan resiko anda sendiri (saya pakai di akun utama saya sih...). Dan kamu masih bisa input data nya secara manual 😀.",
"q3": "Akun nya bisa kamu hack dong?",
"a3": [
"Paimon.moe tidak menyimpan apapun yang berkaitan dengan akun mu (bahkan uid atau nickname mu), jadi jawabannya adalah tidak. Projek ini open source di",
", Saya belum berencana untuk merusak reputasi saya dengan meng-hack akun orang lain, tidak ada gunanya..."
],
"q4": "Hey saya coba cek request nya, tapi kok malah me-request ke domain ini, bukan ke API MiHoYo?",
"a4": [
"Paimon.moe tidak bisa langsung me-request ke API MiHoYo karena",
", jadi request nya di redirect ke proxy cors agar bisa digunakan. Kamu bisa melihat source code nya di",
"sini"
],
"q5": "Apakah kamu menyimpan key sementara ku atau riwayat history ku?",
"a5": "Paimon.moe tidak menyimpan key mu, dan menggunakan HTTPS untuk mengirim link mu ke proxy cors sehingga bisa digunakan. Semua data riwayat wish mu disimpan pada device masing-masing (atau google drive mu jika kamu menyalakan sync di setting). Paimon.moe tidak menyimpan apapun di server (ya apapun)",
"q6": "Saya udah coba step-step nya, tapi kok malah dapet API error?",
"a6": "Coba pastiin sudah di copy semua text nya (coba select all aja text nya, jangan di potong-potong), mungkin ada yang kelewatan, jadi ada yang terpotong"
},
"guide": {
"pc": [
"Buka menu Paimon [ESC]",
"Klik Masukan",
"Tunggu sampai sudah ke-load dan browser mu akan terbuka",
"Copy & paste link nya ke input dibawah",
"Paste link disini... https://webstatic..."
],
"android": [
"Buka menu Paimon",
"Tekan Masukan",
"Tunggu sampai sudah ke-load dan halaman masukan harusnya akan terbuka",
"Matikan wifi dan data selular",
"Tekan refresh di ujung kanan atas",
"Halaman nya seharusnya error dan akan menampilkan text dengan warna hitam",
"Tekan dan tahan text nya lalu pilih semua, kemudian copy text nya (jangan copy hanya sebagian text nya, jangan dipotong-potong lagi)",
"Nyalakan lagi wifi atau data selular mu",
"Paste text nya ke input dibawah",
"Paste text disini... Webpage not available..."
],
"ios": [
"Buka menu Paimon",
"Tekan Masukan",
"Tunggu sampai sudah ke-load dan halaman masukan harusnya akan terbuka",
"Tekan Masalah Game",
"Tekan Mode Co-Op",
"Ada link warna biru di bawah reply nya, klik link nya",
"Browser mu akan terbuka, copy link nya dan paste ke input dibawah",
"Paste link disini... https://genshin.mihoyo..."
]
}
},
"help": {
"title": "Wish Counter Bantuan & Pengaturan",
"enableManual": "Nyalakan Manual Input",
"notice": "Menggunakan Import Otomatis dan Manual Input secara bersamaan tidak direkomendasikan karena belum stabil, dan masih perlu di testing!",
"consider": "Pertimbangkan untuk menggunakan Import Otomatis dulu, buka menu nya di tombol di sebelah tombol yang kamu gunakan untuk membuka halaman bantuan ini",
"howto": {
"title": "Cara menggunakan manual input",
"subtitle": "Setelah kamu melakukan x1 pull wish:",
"press": "Tekan",
"whenYouGet": "ketika kamu mendapatkan",
"p1": "Itu akan otomatis menambahkan total pull",
"and": "dan",
"p2": ["Ketika", "pity mencapai 10, angkanya akan otomatis reset menjadi 0"],
"p3": ["Ketika", "pity mencapai 90, angkanya akan otomatis reset menjadi 0"],
"p4": [
"Ketika kamu melakukan wish x10 pull, tekan",
"tapi counter pity nya tidak akan akurat, karena tidak tahu kapan drop nya terjadi (bisa saja kamu dapat nya saat pull ke 1 atau bisa saja ke 10). Untuk membuat counter nya akurat, kamu perlu mengecek nya di tabel riwayat wish mu dan tabahkan 1-per-1 seperti kamu melakukan 1x pull wish."
],
"p5": ["Kamu juga bisa menekan", "untuk mengedit nilai nya secara manual!"],
"p6": [
"Tekan tombol panah dibawah untuk melihat detail riwayat mu. Sebuah form akan muncul ketika kamu mendapat",
"atau",
"Atau kamu bisa menambahkan atau mengedit tabel nya secara manual."
]
}
}
},
"calculator": {
"titleWeapon": "Kalulator Senjata",
"titleCharacter": "Kalkulator Karakter",
"goto": "Ke",
"howToUse": "Cara Penggunaan",
"guide": {
"clickToMaximize": "Klik gambar untuk memperbesar",
"howToWeapon": "Cara menggunakan Kalkulator Senjata",
"howToCharacter": "Cara menggunakan Kalkulator Karakter"
},
"weapon": {
"calculateAscension": "Hitung Material Ascension?",
"selectRarity": "Pilih bintang senjata",
"selectWeapon": "Pilih senjata",
"current": "Level Senjata, Exp, & Ascension Saat Ini",
"inputCurrentLevel": "Masukkan level senjata saat ini...",
"inputCurrentExp": "Masukkan exp senjata saat ini...",
"intended": "Level Senjata & Ascension Yang Diinginkan",
"inputIntendedLevel": "Masukkan level senjata yang diinginkan...",
"resource": "Resource yang digunakan",
"calculate": "Hitung",
"unknownInformation": "Ada beberapa informasi yang tidak diketahui",
"ascensionLevel": "Level Ascension",
"mora": "Mora (kurang lebih ±40)",
"expWasted": "EXP Terbuang",
"addToTodo": "Tambah ke Todo List",
"addedToTodo": "Sudah ditambahkan ke Todo List"
},
"character": {
"calculateAscension": "Hitung Material Ascension?",
"selectCharacter": "Pilih karakter",
"current": "Level Karakter, Exp, & Ascension Saat Ini",
"inputCurrentLevel": "Masukkan level karakter saat ini...",
"inputCurrentExp": "Masukkan exp karakter saat ini...",
"intended": "Level Karakter & Ascension Yang Diinginkan",
"inputIntendedLevel": "Masukkan level karakter yang diinginkan...",
"resource": "Resource yang digunakan",
"calculateTalent": "Hitung Material Talent?",
"inputTalentLevel": "Masukkan level talent ke 1, 2 dan 3 saat ini",
"inputTalentNotice": "Jika warna level nya berbeda, kurangi 3",
"inputTalent": ["lvl talent ke 1", "lvl talent ke 2", "lvl talent ke 3"],
"talentToLevel": "ke level",
"calculate": "Hitung",
"unknownInformation": "Ada beberapa informasi yang tidak diketahui",
"ascensionLevel": "Level Ascension",
"mora": "Mora (kurang lebih ±40)",
"expWasted": "EXP Terbuang",
"addToTodo": "Tambah ke Todo List",
"addedToTodo": "Sudah ditambahkan ke Todo List"
},
"expTable": {
"level": "Level",
"items": "Items",
"wasted": "Exp Terbuang",
"mora": "Jumlah Mora"
}
},
"items": {
"title": "Daftar Item",
"subtitle": "Klik gambar item untuk menambahkannya ke todo list",
"searchCharacter": "Cari karakter",
"searchWeapon": "Cari senjata",
"day": "Hari",
"material": "Material",
"characterWeapons": "Karakter & Senjata",
"add": {
"rarity": "Pilih Rarity",
"amount": "Jumlah",
"inputAmount": "Masukkan jumlah...",
"cancel": "Batal",
"add": "Tambah ke Todo"
}
},
"days": {
"Sunday": "Minggu",
"Monday": "Senin",
"Tuesday": "Selasa",
"Wednesday": "Rabu",
"Thursday": "Kamis",
"Friday": "Jumat",
"Saturday": "Sabtu"
},
"todo": {
"title": "Todo List",
"summary": "Summary",
"empty": ["Belum ada yang ditambahkan 😀", "Tambahkan todo dari halaman Items atau dari Kalkulator!"],
"farmableToday": "Bisa di farm hari ini",
"resin": "resin diperlukan",
"based": "Berdasarkan AR:{ar} and WL:{wl}",
"change": "(ganti di settings)",
"approximation": "Perkiraan dikalkulasi berdasarkan data dari",
"delete": {
"title": "Hapus todo ini?",
"cancel": "Batal",
"delete": "Hapus"
}
},
"timeline": {
"title": "Timeline",
"localTime": "Tampilkan sebagai waktu lokal",
"starting": "Dimulai dalam",
"ending": "Selesai dalam",
"live": "Live Sekarang!",
"finished": "Sudah Selesai"
},
"settings": {
"version": "Versi Data:",
"multiple": "Punya beberapa akun? Pilih akun disini untuk memisahkan data todo dan riwayat wish",
"selectAccount": "Pilih akun mu",
"reset": "Reset",
"delete": "Hapus",
"add": "Tambah",
"server": "Pilih server mu:",
"drive": [
"Paimon.moe use menggunakan folder Application Data di Google Drive mu untuk menyimpan riwayat wish dan todo list.",
"Paimon.moe hanya bisa membaca dan menulis file yang dibuat oleh website ini."
],
"driveError": "Google Drive API tidak bisa dimuat",
"driveSignIn": "Sign in ke Google Drive",
"driveSignOut": "Sign out dari Google Drive",
"synced": "Synced",
"waiting": "Menunggu...",
"syncing": "Syncing...",
"lastSync": "Sync Terakhir:",
"feedback": "Jika kamu menemukan bug, data yang salah, atau ada feedback, kamu bisa chat di",
"or": "atau",
"thanks": "Thanks😁!",
"modal": {
"notice": "Semua todo dan riwayat wish akan dihapus",
"cancel": "Cancel",
"delete": "Delete",
"reset": "Reset"
}
}
}

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import { mdiCheckCircleOutline, mdiClose, mdiInformationOutline } from '@mdi/js';
@ -464,13 +466,19 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-4">
<div>
<div>
<Check className="mb-2" on:change={onChange} bind:checked={withAscension}>Calculate Ascension Material?</Check>
<Check className="mb-2" on:change={onChange} bind:checked={withAscension}
>{$t('calculator.character.calculateAscension')}</Check
>
{#if withAscension}
<CharacterSelect on:change={onChange} bind:selected={selectedCharacter} placeholder="Select character" />
<CharacterSelect
on:change={onChange}
bind:selected={selectedCharacter}
placeholder={$t('calculator.character.selectCharacter')}
/>
{/if}
<div>
<p class="text-white text-center mt-3 mb-2">Current Character Level, Exp, & Ascension</p>
<p class="text-white text-center mt-3 mb-2">{$t('calculator.character.current')}</p>
<Input
className="mb-2"
on:change={onChange}
@ -478,20 +486,22 @@
min={1}
max={80}
bind:value={currentLevel}
placeholder="Input current character level..." />
placeholder={$t('calculator.character.inputCurrentLevel')}
/>
<Input
className="mb-2"
on:change={onChange}
type="number"
min={0}
bind:value={currentExp}
placeholder="Input current character exp..." />
placeholder={$t('calculator.character.inputCurrentExp')}
/>
{#if withAscension}
<AscensionSelector min={minAscension} bind:value={currentAscension} on:change={onChange} />
{/if}
</div>
<div>
<p class="text-white text-center mt-3 mb-2">Intended Character Level & Ascension</p>
<p class="text-white text-center mt-3 mb-2">{$t('calculator.character.intended')}</p>
<Input
className="mb-2"
on:change={onChange}
@ -499,18 +509,20 @@
min={currentLevel}
max={80}
bind:value={intendedLevel}
placeholder="Input intended character level..." />
placeholder={$t('calculator.character.inputIntendedLevel')}
/>
{#if withAscension}
<AscensionSelector
min={Math.max(currentAscension, minIntendedAscension)}
bind:value={intendedAscension}
on:change={onChange} />
on:change={onChange}
/>
{/if}
</div>
</div>
</div>
<div class="flex flex-col pl-1">
<p class="text-white text-center md:text-left mb-1">Resource to Use</p>
<p class="text-white text-center md:text-left mb-1">{$t('calculator.character.resource')}</p>
{#each resources as res}
<div class="mb-1">
<Checkbox disabled={res.disabled} bind:checked={res.selected} on:change={onChange}>
@ -527,10 +539,11 @@
{/each}
<div class="mt-4">
{#if withAscension}
<Check on:change={onChange} bind:checked={withTalent}>Calculate Talent Material?</Check>
<Check on:change={onChange} bind:checked={withTalent}>{$t('calculator.character.calculateTalent')}</Check>
{/if}
{#if withTalent}
<p class="text-white text-center mt-3">Input the 1st, 2nd & 3rd current talent level</p>
<p class="text-white text-center mt-3">{$t('calculator.character.inputTalentLevel')}</p>
<p class="text-green-300 text-center">{$t('calculator.character.inputTalentNotice')}</p>
<div class="grid grid-cols-3 gap-2 mt-2">
<Input
on:change={onChange}
@ -538,23 +551,26 @@
min={1}
max={maxTalentLevel}
bind:value={currentTalentLevel.first}
placeholder="1st talent lvl" />
placeholder={$t('calculator.character.talent.0')}
/>
<Input
on:change={onChange}
type="number"
min={1}
max={maxTalentLevel}
bind:value={currentTalentLevel.second}
placeholder="2nd talent lvl" />
placeholder={$t('calculator.character.talent.1')}
/>
<Input
on:change={onChange}
type="number"
min={1}
max={maxTalentLevel}
bind:value={currentTalentLevel.third}
placeholder="3rd talent lvl" />
placeholder={$t('calculator.character.talent.2')}
/>
</div>
<p class="text-white text-center mt-3">to level</p>
<p class="text-white text-center mt-3">{$t('calculator.character.talentToLevel')}</p>
<div class="grid grid-cols-3 gap-2 mt-2">
<Input
on:change={onChange}
@ -562,36 +578,41 @@
min={currentTalentLevel.first}
max={maxTalentLevel}
bind:value={targetTalentLevel.first}
placeholder="1st talent lvl" />
placeholder={$t('calculator.character.talent.0')}
/>
<Input
on:change={onChange}
type="number"
min={currentTalentLevel.second}
max={maxTalentLevel}
bind:value={targetTalentLevel.second}
placeholder="2nd talent lvl" />
placeholder={$t('calculator.character.talent.1')}
/>
<Input
on:change={onChange}
type="number"
min={currentTalentLevel.third}
max={maxTalentLevel}
bind:value={targetTalentLevel.third}
placeholder="3rd talent lvl" />
placeholder={$t('calculator.character.talent.2')}
/>
</div>
{/if}
</div>
</div>
<div class="md:col-span-2 xl:col-span-1">
<Button disabled={!canCalculate} className="block w-full md:w-auto" on:click={calculate}>Calculate</Button>
<Button disabled={!canCalculate} className="block w-full md:w-auto" on:click={calculate}
>{$t('calculator.character.calculate')}</Button
>
{#if currentMax !== null && !changed}
{#if Object.keys(unknownList).length > 0}
<div class="border-2 border-red-400 rounded-xl mt-2 p-4 md:inline-block">
<p class="font-bold flex items-center text-red-400">
<Icon className="mr-1 mb-1" path={mdiInformationOutline} />
There are some unknown information
{$t('calculator.character.talent.2')}
</p>
{#each Object.entries(unknownList) as [title, values]}
<p class="text-red-400">Ascension level {Number(title) + 1}</p>
<p class="text-red-400">{$t('calculator.character.ascensionLevel')} {Number(title) + 1}</p>
<ul>
{#each values as val}
<li class="pl-4 text-red-400">- {val}</li>
@ -606,8 +627,10 @@
{#if currentMax.usage[i] > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span>
<span class="text-white mr-2 whitespace-no-wrap"
>{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span
>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
@ -626,8 +649,10 @@
{#if item.amount > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{item.amount}
<Icon size={0.5} path={mdiClose} /></span>
<span class="text-white mr-2 whitespace-no-wrap"
>{item.amount}
<Icon size={0.5} path={mdiClose} /></span
>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
@ -644,8 +669,10 @@
{#if item.amount > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{item.amount}
<Icon size={0.5} path={mdiClose} /></span>
<span class="text-white mr-2 whitespace-no-wrap"
>{item.amount}
<Icon size={0.5} path={mdiClose} /></span
>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
@ -660,22 +687,24 @@
{/each}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span>
<span class="text-white mr-2 whitespace-no-wrap"
>{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span
>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src="/images/mora.png" alt="Mora" />
</span>
Mora (approximate)
{$t('calculator.character.mora')}
</span>
</td>
</tr>
{#if currentMax.over < 0}
<tr>
<td />
<td class="text-red-400 py-1">{currentMax.over * -1} EXP Wasted</td>
<td class="text-red-400 py-1">{currentMax.over * -1} {$t('calculator.character.expWasted')}</td>
</tr>
{/if}
</table>
@ -683,9 +712,9 @@
{#if addedToTodo}
<span class="text-green-400" in:fade={{ duration: 100 }}>
<Icon path={mdiCheckCircleOutline} size={0.8} />
Added to Todo List
{$t('calculator.character.addedToTodo')}
</span>
{:else}<span in:fade={{ duration: 100 }}>Add to Todo List </span>{/if}
{:else}<span in:fade={{ duration: 100 }}>{$t('calculator.character.addToTodo')}</span>{/if}
</Button>
</div>
{/if}

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { mdiArrowRight } from '@mdi/js';
import Icon from '../../components/Icon.svelte';
@ -112,10 +114,10 @@
<div class="bg-item rounded-xl p-4 w-full">
<table>
<tr>
<th class="px-2 font-display text-gray-400" colspan="3">Level</th>
<th class="px-2 font-display text-gray-400 align-bottom">Items</th>
<th class="px-2 font-display text-gray-400 align-bottom">Wasted Exp</th>
<th class="px-2 font-display text-gray-400 align-bottom text-right">Mora Cost</th>
<th class="px-2 font-display text-gray-400" colspan="3">{$t('calculator.expTable.level')}</th>
<th class="px-2 font-display text-gray-400 align-bottom">{$t('calculator.expTable.items')}</th>
<th class="px-2 font-display text-gray-400 align-bottom">{$t('calculator.expTable.wasted')}</th>
<th class="px-2 font-display text-gray-400 align-bottom text-right">{$t('calculator.expTable.mora')}</th>
</tr>
{#each result as row, i}
<tr>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import { mdiStar, mdiClose, mdiInformationOutline, mdiCheckCircleOutline } from '@mdi/js';
@ -332,20 +334,27 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-4">
<div>
<div>
<Check className="mb-2" on:change={onChange} bind:checked={withAscension}>Calculate Ascension Material?</Check>
<Check className="mb-2" on:change={onChange} bind:checked={withAscension}
>{$t('calculator.weapon.calculateAscension')}</Check
>
{#if !withAscension}
<Select
on:change={onChange}
bind:selected={rarity}
icon={mdiStar}
options={weaponsRarity}
placeholder="Select weapon rarity" />
placeholder={$t('calculator.weapon.selectRarity')}
/>
{:else}
<WeaponSelect on:change={onChange} bind:selected={selectedWeapon} placeholder="Select weapon" />
<WeaponSelect
on:change={onChange}
bind:selected={selectedWeapon}
placeholder={$t('calculator.weapon.selectWeapon')}
/>
{/if}
<div>
<p class="text-white text-center mt-3 mb-2">Current Weapon Level, Exp, & Ascension</p>
<p class="text-white text-center mt-3 mb-2">{$t('calculator.weapon.current')}</p>
<Input
className="mb-2"
on:change={onChange}
@ -353,20 +362,22 @@
min={1}
max={80}
bind:value={currentLevel}
placeholder="Input current weapon level..." />
placeholder={$t('calculator.weapon.inputCurrentLevel')}
/>
<Input
className="mb-2"
on:change={onChange}
type="number"
min={0}
bind:value={currentExp}
placeholder="Input current weapon exp..." />
placeholder={$t('calculator.weapon.inputCurrentExp')}
/>
{#if withAscension}
<AscensionSelector min={minAscension} bind:value={currentAscension} on:change={onChange} />
{/if}
</div>
<div>
<p class="text-white text-center mt-3 mb-2">Intended Weapon Level & Ascension</p>
<p class="text-white text-center mt-3 mb-2">{$t('calculator.weapon.intended')}</p>
<Input
className="mb-2"
on:change={onChange}
@ -374,18 +385,20 @@
min={currentLevel}
max={80}
bind:value={intendedLevel}
placeholder="Input intended weapon level..." />
placeholder={$t('calculator.weapon.inputIntendedLevel')}
/>
{#if withAscension}
<AscensionSelector
min={Math.max(currentAscension, minIntendedAscension)}
bind:value={intendedAscension}
on:change={onChange} />
on:change={onChange}
/>
{/if}
</div>
</div>
</div>
<div class="flex flex-col pl-1">
<p class="text-white text-center md:text-left mb-1">Resource to Use</p>
<p class="text-white text-center md:text-left mb-1">{$t('calculator.weapon.resource')}</p>
{#each resources as res}
<div class="mb-1">
<Checkbox disabled={res.disabled} bind:checked={res.selected} on:change={onChange}>
@ -402,16 +415,18 @@
{/each}
</div>
<div class="md:col-span-2 xl:col-span-1">
<Button disabled={!canCalculate} className="block w-full md:w-auto" on:click={calculate}>Calculate</Button>
<Button disabled={!canCalculate} className="block w-full md:w-auto" on:click={calculate}
>{$t('calculator.weapon.calculate')}</Button
>
{#if currentMax !== null && !changed}
{#if Object.keys(unknownList).length > 0}
<div class="border-2 border-red-400 rounded-xl mt-2 p-4 md:inline-block">
<p class="font-bold flex items-center text-red-400">
<Icon className="mr-1 mb-1" path={mdiInformationOutline} />
There are some unknown information
{$t('calculator.weapon.unknownInformation')}
</p>
{#each Object.entries(unknownList) as [title, values]}
<p class="text-red-400">Ascension level {Number(title) + 1}</p>
<p class="text-red-400">{$t('calculator.weapon.ascensionLevel')} {Number(title) + 1}</p>
<ul>
{#each values as val}
<li class="pl-4 text-red-400">- {val}</li>
@ -426,8 +441,10 @@
{#if currentMax.usage[i] > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span>
<span class="text-white mr-2 whitespace-no-wrap"
>{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span
>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
@ -446,8 +463,10 @@
{#if item.amount > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{item.amount}
<Icon size={0.5} path={mdiClose} /></span>
<span class="text-white mr-2 whitespace-no-wrap"
>{item.amount}
<Icon size={0.5} path={mdiClose} /></span
>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
@ -462,22 +481,24 @@
{/each}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span>
<span class="text-white mr-2 whitespace-no-wrap"
>{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span
>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src="/images/mora.png" alt="Mora" />
</span>
Mora (approximate ±40)
{$t('calculator.weapon.mora')}
</span>
</td>
</tr>
{#if currentMax.over < 0}
<tr>
<td />
<td class="text-red-400 py-1">{currentMax.over * -1} EXP Wasted</td>
<td class="text-red-400 py-1">{currentMax.over * -1} {$t('calculator.weapon.expWasted')}</td>
</tr>
{/if}
</table>
@ -485,9 +506,9 @@
{#if addedToTodo}
<span class="text-green-400" in:fade={{ duration: 100 }}>
<Icon path={mdiCheckCircleOutline} size={0.8} />
Added to Todo List
{$t('calculator.weapon.addedToTodo')}
</span>
{:else}<span in:fade={{ duration: 100 }}>Add to Todo List </span>{/if}
{:else}<span in:fade={{ duration: 100 }}>{$t('calculator.weapon.addToTodo')}</span>{/if}
</Button>
</div>
{/if}

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { getContext } from 'svelte';
import { mdiArrowDown, mdiArrowUp, mdiHelpCircle } from '@mdi/js';
@ -53,7 +55,7 @@
<div class="flex justify-center md:justify-start mb-4">
<Button on:click={openHowTo}>
<Icon size={0.8} path={mdiHelpCircle} />
How To Use
{$t('calculator.howToUse')}
</Button>
</div>
<div
@ -62,12 +64,13 @@
>
<Button on:click={() => scroll('character')}>
<Icon size={0.8} path={mdiArrowDown} />
Go To Character Calculator
{$t('calculator.goto')}
{$t('calculator.titleCharacter')}
</Button>
<h1
class="font-display font-black text-center mt-2 md:mt-0 md:mr-2 xl:mr-8 text-3xl lg:text-left lg:text-5xl text-white"
>
Weapon Calculator
{$t('calculator.titleWeapon')}
</h1>
</div>
<WeaponCalculator />
@ -77,12 +80,13 @@
>
<Button on:click={() => scroll('weapon')}>
<Icon size={0.8} path={mdiArrowUp} />
Go To Weapon Calculator
{$t('calculator.goto')}
{$t('calculator.titleWeapon')}
</Button>
<h1
class="font-display font-black text-center mt-2 md:mt-0 md:mr-2 xl:mr-8 text-3xl lg:text-left lg:text-5xl text-white"
>
Character Calculator
{$t('calculator.titleCharacter')}
</h1>
</div>
<CharacterCalculator />

View File

@ -1,4 +1,5 @@
<script>
import { t } from 'svelte-i18n'
import { mdiStar } from '@mdi/js';
import Icon from '../components/Icon.svelte';
@ -77,9 +78,9 @@
/>
</svelte:head>
<div class="lg:ml-64 pt-20 lg:pt-8">
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">Characters</h1>
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">{$t('characters.title')}</h1>
<p class="text-gray-400 px-4 md:px-8 font-medium pb-4" style="margin-top: -1rem;">
Stat numbers are at level 80 Ascension 6. You can also click the header to sort!
{$t('characters.subtitle')}
</p>
<div class="block overflow-x-auto whitespace-no-wrap pb-8">
@ -87,24 +88,24 @@
<table class="w-full block p-4 bg-item rounded-xl">
<thead>
<th style="min-width: 4rem;" />
<TableHeader on:click={() => sort('name')} sort={sortBy === 'name'} order={sortOrder}>Name</TableHeader>
<TableHeader on:click={() => sort('name')} sort={sortBy === 'name'} order={sortOrder}>{$t('characters.name')}</TableHeader>
<TableHeader on:click={() => sort('element')} sort={sortBy === 'element'} order={sortOrder} align="center">
Element
{$t('characters.element')}
</TableHeader>
<TableHeader on:click={() => sort('rarity')} sort={sortBy === 'rarity'} order={sortOrder} align="center">
Rarity
{$t('characters.rarity')}
</TableHeader>
<TableHeader on:click={() => sort('weapon')} sort={sortBy === 'weapon'} order={sortOrder} align="center">
Weapon
{$t('characters.weapon')}
</TableHeader>
<TableHeader on:click={() => sort('hp')} sort={sortBy === 'hp'} order={sortOrder} align="center">
HP
{$t('characters.hp')}
</TableHeader>
<TableHeader on:click={() => sort('atk')} sort={sortBy === 'atk'} order={sortOrder} align="center">
ATK
{$t('characters.atk')}
</TableHeader>
<TableHeader on:click={() => sort('def')} sort={sortBy === 'def'} order={sortOrder} align="center">
DEF
{$t('characters.def')}
</TableHeader>
</thead>
<tbody>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { getContext } from 'svelte';
import { characters } from '../data/characters';
@ -159,26 +161,26 @@
/>
</svelte:head>
<div class="lg:ml-64 pt-20 lg:pt-8">
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">Item List</h1>
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">{$t('items.title')}</h1>
<p class="text-gray-400 px-4 md:px-8 font-medium pb-4" style="margin-top: -1rem;">
Click the item image to add it to the todo list
{$t('items.subtitle')}
</p>
<div class="pb-4 px-4 md:px-8 grid grid-cols-1 md:grid-cols-2 gap-2 max-w-screen-md">
<CharacterSelect bind:selected={selectedCharacter} placeholder="Search character" />
<WeaponSelect bind:selected={selectedWeapon} placeholder="Search weapon" />
<CharacterSelect bind:selected={selectedCharacter} placeholder={$t('items.searchCharacter')} />
<WeaponSelect bind:selected={selectedWeapon} placeholder={$t('items.searchWeapon')} />
</div>
<div class="block overflow-x-auto whitespace-no-wrap pb-8">
<div class="px-4 md:px-8 table max-w-full">
<table class="w-full block p-4 bg-item rounded-xl">
<thead>
<th class="text-gray-400 select-none font-display text-lg text-left px-4 pb-2 border-gray-700 border-b">
Days
{$t('items.day')}
</th>
<th class="text-gray-400 select-none font-display text-lg text-left px-4 pb-2 border-gray-700 border-b">
Materials
{$t('items.material')}
</th>
<th class="text-gray-400 select-none font-display text-lg text-left px-4 pb-2 border-gray-700 border-b">
Characters & Weapons
{$t('items.characterWeapons')}
</th>
</thead>
<tbody>
@ -191,7 +193,7 @@
? 'border-gray-700 border-b py-2'
: 'py-2'}
>
{#if index === 0}{dayArr[0]}<br />{dayArr[1]}{/if}
{#if index === 0}{$t(`days.${dayArr[0]}`)}<br />{$t(`days.${dayArr[1]}`)}{/if}
</td>
<td class="border-gray-700 border-b text-center align-middle py-2">
<div class="flex items-center">
@ -234,7 +236,8 @@
? 'border-gray-700 border-b py-2'
: 'py-2'}
>
{#if index === 0 && Object.entries(charactersDays[day]).length === 0}{dayArr[0]}<br />{dayArr[1]}{/if}
{#if index === 0 && Object.entries(charactersDays[day]).length === 0}{$t(`days.${dayArr[0]}`)}<br
/>{$t(`days.${dayArr[1]}`)}{/if}
</td>
<td class="border-gray-700 border-b text-center py-2">
<div class="flex items-center">
@ -279,10 +282,10 @@
<table class="w-full block p-4 bg-item rounded-xl">
<thead>
<th class="text-gray-400 select-none font-display text-lg text-left px-4 pb-2 border-gray-700 border-b">
Material
{$t('items.material')}
</th>
<th class="text-gray-400 select-none font-display text-lg text-left px-4 pb-2 border-gray-700 border-b">
Characters & Weapons
{$t('items.characterWeapons')}
</th>
</thead>
<tbody>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import Button from '../../components/Button.svelte';
export let account;
@ -7,10 +9,10 @@
</script>
<div>
<p class="text-white font-bold mb-4 text-lg">Delete {account.label}?</p>
<p class="text-white mb-4">All todo and wish history data will be deleted</p>
<p class="text-white font-bold mb-4 text-lg">{$t('settings.modal.delete')} {account.label}?</p>
<p class="text-white mb-4">{$t('settings.modal.notice')}</p>
<div class="flex justify-end gap-2">
<Button on:click={cancel}>Cancel</Button>
<Button on:click={deleteAccount} color="red">Delete</Button>
<Button on:click={cancel}>{$t('settings.modal.cancel')}</Button>
<Button on:click={deleteAccount} color="red">{$t('settings.modal.delete')}</Button>
</div>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import Button from '../../components/Button.svelte';
export let account;
@ -7,10 +9,10 @@
</script>
<div>
<p class="text-white font-bold mb-4 text-lg">Reset {account.label}?</p>
<p class="text-white mb-4">All todo and wish history data will be deleted</p>
<p class="text-white font-bold mb-4 text-lg">{$t('settings.modal.reset')} {account.label}?</p>
<p class="text-white mb-4">{$t('settings.modal.notice')}</p>
<div class="flex justify-end gap-2">
<Button on:click={cancel}>Cancel</Button>
<Button on:click={resetAccount} color="red">Reset</Button>
<Button on:click={cancel}>{$t('settings.modal.cancel')}</Button>
<Button on:click={resetAccount} color="red">{$t('settings.modal.reset')}</Button>
</div>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { mdiCheckCircleOutline, mdiChevronDown, mdiDiscord, mdiGithub, mdiGoogleDrive, mdiLoading } from '@mdi/js';
import { getContext, onMount } from 'svelte';
import { slide } from 'svelte/transition';
@ -239,28 +241,30 @@
<div class="lg:ml-64 pt-20 px-4 md:px-8 lg:pt-8">
<div class="bg-item rounded-xl mb-4 p-4">
<p class="text-white">Data Version: <b>1.3</b></p>
<p class="text-white">{$t('settings.version')} <b>1.3</b></p>
</div>
<div class="bg-item rounded-xl mb-4 p-4 flex flex-col">
<p class="text-white">Have multiple account? Choose account here to separate your wish and todo data</p>
<div class="flex mt-2">
<p class="text-white">{$t('settings.multiple')}</p>
<div class="flex flex-col md:flex-row mt-2">
<Select
className="w-64 mr-2"
bind:selected={currentAccount}
options={$accounts}
placeholder="Select your account"
placeholder={$t('settings.selectAccount')}
/>
<Button on:click={openResetAccount} className="mr-2 w-24" color="red">Reset</Button>
{#if currentAccount.value !== 'main'}
<Button on:click={openDeleteAccount} className="mr-2 w-24" color="red">Delete</Button>
{/if}
<Button className="w-24" on:click={addAccount}>Add</Button>
<div class="flex flex-1 mt-2 md:mt-0">
<Button on:click={openResetAccount} className="mr-2 w-24" color="red">{$t('settings.reset')}</Button>
{#if currentAccount.value !== 'main'}
<Button on:click={openDeleteAccount} className="mr-2 w-24" color="red">{$t('settings.delete')}</Button>
{/if}
<Button className="w-24" on:click={addAccount}>{$t('settings.add')}</Button>
</div>
</div>
</div>
<div class="bg-item rounded-xl mb-4 p-4 flex flex-col md:flex-row">
<div class="flex flex-col md:flex-row md:items-center mr-2">
<p class="text-white mr-2">Select your server:</p>
<Select className="w-64" bind:selected={selectedServer} options={servers} placeholder="Select your server" />
<p class="text-white mr-2">{$t('settings.server')}</p>
<Select className="w-64" bind:selected={selectedServer} options={servers} placeholder={$t('settings.server')} />
</div>
<div class="flex mt-2 md:mt-0">
<div class="flex flex-col md:flex-row md:items-center w-32 mr-2">
@ -274,31 +278,33 @@
</div>
</div>
<div class="bg-item rounded-xl mb-4 p-4">
<p class="text-white mb-2">
Paimon.moe use Application Data Directory on your Google Drive to save and sync your wish counter and todo list.
</p>
<p class="text-white mb-4">Paimon.moe can only read and write file that this site create.</p>
<p class="text-white mb-2">{$t('settings.drive.0')}</p>
<p class="text-white mb-4">{$t('settings.drive.1')}</p>
{#if $driveLoading}
<Icon path={mdiLoading} color="white" spin />
{:else if $driveError}
<Button color="red">
<Icon path={mdiGoogleDrive} className="mr-2" />
Google Drive API cannot be loaded
{$t('settings.driveError')}
</Button>
{:else if !$driveSignedIn}
<Button on:click={signIn}>
<Icon path={mdiGoogleDrive} className="mr-2" />
Sign in to Google Drive
{$t('settings.driveSignIn')}
</Button>
{:else}
<Button on:click={signOut}>
<Icon path={mdiGoogleDrive} className="mr-2" />
Sign out Google Drive
{$t('settings.driveSignOut')}
</Button>
<p class="text-white mt-4">
Sync Status:
<span class={`font-bold ${isSynced ? 'text-green-400' : 'text-yellow-400'}`}>
{isSynced ? 'Synced' : $localModified && $synced ? 'Waiting...' : 'Syncing...'}
{isSynced
? $t('settings.synced')
: $localModified && $synced
? $t('settings.waiting')
: $t('settings.syncing')}
{#if isSynced}
<Icon path={mdiCheckCircleOutline} className="text-green-400" />
{:else if $localModified && !$synced}
@ -307,20 +313,26 @@
</span>
</p>
{#if $lastSyncTime !== null}
<p class="text-gray-400">Last Sync: {$lastSyncTime.format('dddd, MMMM D, YYYY h:mm:ss A')}</p>
<p class="text-gray-400">{$t('settings.lastSync')} {$lastSyncTime.format('dddd, MMMM D, YYYY h:mm:ss A')}</p>
{/if}
{/if}
</div>
<div class="bg-item rounded-xl mb-4 p-4 text-white">
If you found any bug, wrong data, or you have any feedback, please PM me on discord
<span class="bg-background rounded-xl pr-2"><Icon path={mdiDiscord} /> Baruna#4422</span> or
{$t('settings.feedback')}
<a
href="https://discord.gg/tPURAYgHV9"
target="__blank"
class="whitespace-no-wrap bg-background rounded-xl pr-2 text-blue-400 hover:underline"
><Icon path={mdiDiscord} /> Discord</a
>
{$t('settings.or')}
<a
href="https://github.com/MadeBaruna/paimon-moe/issues"
target="__blank"
class="whitespace-no-wrap bg-background rounded-xl pr-2 text-blue-400 hover:underline"
><Icon path={mdiGithub} /> Github Issues</a
>
Thanks😁!
{$t('settings.thanks')}
</div>
<div class="bg-item rounded-xl mb-4 p-4 text-white">
<p class="cursor-pointer" on:click={toggleChangelog}>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import dayjs from 'dayjs';
import { onMount } from 'svelte';
@ -36,13 +38,13 @@
</p>
<p class="text-gray-400 px-4 py-1 bg-black bg-opacity-50 rounded-xl mt-2 inline-block">
{#if !started}
Starting in {dayjs.duration(diffStart).format(diffStart > 86400000 ? 'D[d] HH:mm:ss' : 'HH:mm:ss')}
{$t('timeline.starting')} {dayjs.duration(diffStart).format(diffStart > 86400000 ? 'D[d] HH:mm:ss' : 'HH:mm:ss')}
{:else if started && !ended && !event.startOnly}
Ending in {dayjs.duration(diffEnd).format(diffEnd > 86400000 ? 'D[d] HH:mm:ss' : 'HH:mm:ss')}
{$t('timeline.ending')} {dayjs.duration(diffEnd).format(diffEnd > 86400000 ? 'D[d] HH:mm:ss' : 'HH:mm:ss')}
{:else if event.startOnly}
Live Now!
{$t('timeline.live')}
{:else}
Finished
{$t('timeline.finished')}
{/if}
</p>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { getContext, onMount, tick } from 'svelte';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
@ -199,10 +201,10 @@
</svelte:head>
<div class="lg:ml-64 pt-20 lg:pt-8">
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">Timeline</h1>
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">{$t('timeline.title')}</h1>
{#if !loading}
<div class="px-4 md:px-8 text-white select-none">
<Checkbox bind:checked={showAsLocalTime}>Show as local time</Checkbox>
<Checkbox bind:checked={showAsLocalTime}>{$t('timeline.localTime')}</Checkbox>
</div>
<div class="w-full overflow-x-auto px-4 md:px-8" bind:this={timelineContainer} on:wheel={transformScroll}>
<div

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { getContext, onMount, tick } from 'svelte';
import { slide } from 'svelte/transition';
import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiClose, mdiInformation, mdiLoading } from '@mdi/js';
@ -283,20 +285,20 @@
</svelte:head>
<div class="lg:ml-64 pt-20 px-2 md:px-8 lg:pt-8">
<Masonry stretchFirst={true} bind:refreshLayout bind:columnCount items={id}>
<h1 class="font-display font-black text-3xl lg:text-left lg:text-5xl text-white">Todo List</h1>
<h1 class="font-display font-black text-3xl lg:text-left lg:text-5xl text-white">{$t('todo.title')}</h1>
<div class="bg-item rounded-xl p-4 text-white">
{#if $loading}
<Icon path={mdiLoading} color="white" spin />
{:else if $todos.length > 0}
<div class="flex items-center mb-4">
<p class="font-bold text-xl mr-2 flex-1">Summary</p>
<p class="font-bold text-xl mr-2 flex-1">{$t('todo.summary')}</p>
</div>
{:else}
<p class="font-bold text-xl">Nothing to do yet 😀<br />Add some from the Items page or the Calculator!</p>
<p class="font-bold text-xl">{$t('todo.empty.0')}<br />{$t('todo.empty.1')}</p>
{/if}
{#if Object.entries(todayOnlyItems).length > 0}
<div class="rounded-xl bg-background px-4 py-2 mb-2">
<p class="font-semibold mb-2 text-center">Farmable Today</p>
<p class="font-semibold mb-2 text-center">{$t('todo.farmableToday')}</p>
<table class="w-full">
{#each Object.entries(todayOnlyItems) as [id, amount]}
<tr class="today-only">
@ -323,7 +325,7 @@
<div class="rounded-xl bg-background px-4 py-2 mb-2">
<div class="flex items-center justify-center cursor-pointer" on:click={toggleResinDetail}>
<img src="/images/resin.png" alt="resin" class="w-6 h-6 mr-2" />
<span class="mr-2"><span class="font-black">{resin}</span> resin needed</span>
<span class="mr-2"><span class="font-black">{resin}</span> {$t('todo.resin')}</span>
<Icon
path={mdiChevronDown}
className={`duration-100 ease-in ${showResinDetail ? 'transform rotate-180' : ''}`}
@ -355,11 +357,11 @@
</table>
<span class="mt-4 block text-center">
Based on AR:{$ar} and WL:{$wl}
{$t('todo.based', { values: { ar: $ar, wl: $wl } })}
</span>
<span class="text-gray-400 text-xs text-center block">(change on settings)</span>
<span class="text-gray-400 text-xs text-center block">{$t('todo.change')}</span>
<span class="mt-2 text-sm block text-center">
Approximation calculated from drop rates by
{$t('todo.approximation')}
<a class="text-primary font-semibold" target="__blank" href="https://discord.gg/ydwdYmr"
>Genshin Impact Data Gathering Discord</a
>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { onMount, getContext } from 'svelte';
import { slide } from 'svelte/transition';
import { mdiPencil, mdiStar, mdiChevronDown } from '@mdi/js';
@ -258,7 +260,7 @@
} rounded-xl flex`}
>
<span class="text-gray-200 whitespace-no-wrap flex-1">
Lifetime Pulls<br />
{$t('wish.lifetimePulls')}<br />
<span class="flex items-center text-gray-600">
<img class="w-4 h-4 mr-2" src="/images/primogem.png" alt="primogem" />
{numberFormat.format(total * 160)}
@ -278,7 +280,7 @@
5
<Icon path={mdiStar} size={0.75} className="mb-1" />
Pity
<br /><span class="text-gray-600">Guaranteed at {legendaryPity}</span>
<br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: legendaryPity } })}</span>
</span>
{#if isEdit}
<Input type="number" min={1} bind:value={legendaryEdit} />
@ -294,7 +296,7 @@
4
<Icon path={mdiStar} size={0.75} className="mb-1" />
Pity
<br /><span class="text-gray-600">Guaranteed at 10</span>
<br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: 10 } })}</span>
</span>
{#if isEdit}
<Input type="number" min={1} bind:value={rareEdit} />

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n'
import { fade } from 'svelte/transition';
import { mdiArrowUp, mdiClose } from '@mdi/js';
@ -19,18 +21,18 @@
<div transition:fade={{ duration: 100 }} class="bg-item p-4 rounded-xl mb-4 text-white flex items-start">
<div class="flex-1">
<p>
Welcome to Paimon.moe Wish Counter! The recommended usage is to import your wish history with the Auto Importer.
{$t('wish.welcome')}
</p>
<p class="mb-2">
To start, press the <span class="bg-background px-2 rounded-xl">Auto Import</span> button up there <Icon
{$t('wish.welcomeStart1')} <span class="bg-background px-2 rounded-xl">{$t('wish.autoImport')}</span> {$t('wish.welcomeStart2')} <Icon
path={mdiArrowUp}
/>
</p>
<p class="text-gray-400">
If you want to manually input the data, you can enable it here: <Button
{$t('wish.manual')} <Button
on:click={enableManual}
className="text-gray-400"
size="sm">Use Manual Input</Button
size="sm">{$t('wish.manualButton')}</Button
>
</p>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n';
import { onMount } from 'svelte';
import { characters } from '../../data/characters';
import { weaponList } from '../../data/weaponList';
@ -148,7 +150,7 @@
<SummaryItem avg={avg[types[3].id]} type={types[3]} />
{/if}
<div class="bg-item rounded-xl p-4 flex items-center w-full text-white mt-4" style="height: min-content;">
Wishes Worth <img class="w-4 h-4 mx-2" src="/images/primogem.png" alt="primogem" />
{$t('wish.wishesWorth')} <img class="w-4 h-4 mx-2" src="/images/primogem.png" alt="primogem" />
{numberFormat.format(totalWish * 160)}
</div>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { t } from 'svelte-i18n'
import { mdiDatabaseImport, mdiHelpCircle } from '@mdi/js';
import { getContext, onMount } from 'svelte';
@ -123,20 +125,20 @@
<h1 class="font-display font-black text-5xl text-white text-center md:text-left md:mr-4">Wish Counter</h1>
<Button className="mr-2 hidden md:block" on:click={openImport}>
<Icon size={0.8} path={mdiDatabaseImport} />
Auto Import
{$t('wish.autoImport')}
</Button>
<Button on:click={openHowTo} className="hidden md:block">
<Icon size={0.8} path={mdiHelpCircle} />
Help & Setting
{$t('wish.helpAndSetting')}
</Button>
<div class="md:hidden flex flex-wrap justify-center">
<Button className="m-1" on:click={openImport}>
<Icon size={0.8} path={mdiDatabaseImport} />
Auto Import
{$t('wish.autoImport')}
</Button>
<Button className="m-1" on:click={openHowTo}>
<Icon size={0.8} path={mdiHelpCircle} />
How To Use
{$t('wish.helpAndSetting')}
</Button>
</div>
</div>

View File

@ -3,6 +3,8 @@ import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';
import { i18nMiddleware } from './i18n.js';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
@ -10,6 +12,7 @@ polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
i18nMiddleware(),
sapper.middleware()
)
.listen(PORT, err => {

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
<!-- /Creative Commons Public Domain -->
<!--
<rdf:RDF xmlns="http://web.resource.org/cc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Work rdf:about="">
<dc:title>New Zealand, Australia, United Kingdom, United States,
Bosnia and Herzegovina, Azerbaijan, Armenia, Bahamas, Belgium, Benin,
Bulgaria, Estonia, Finland, Gabon, Gambia, Germany, Greece, Greenland,
Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags</dc:title>
<dc:rights><Agent>
<dc:title>Daniel McRae</dc:title>
</Agent></dc:rights>
<license rdf:resource="http://web.resource.org/cc/PublicDomain" />
</Work>
<License rdf:about="http://web.resource.org/cc/PublicDomain">
<permits rdf:resource="http://web.resource.org/cc/Reproduction" />
<permits rdf:resource="http://web.resource.org/cc/Distribution" />
<permits rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
</License>
</rdf:RDF>
-->
<svg id="svg1" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="512" width="512" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata id="metadata2995">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs3">
<clipPath id="clipPath3224" clipPathUnits="userSpaceOnUse">
<rect id="rect3226" fill-opacity="0.67" height="500" width="500" y=".0000023437" x="250"/>
</clipPath>
</defs>
<g id="flag" clip-path="url(#clipPath3224)" transform="matrix(1.024 0 0 1.024 -256 -0.0000024)">
<g id="g578" stroke-width="1pt" transform="scale(16.667)">
<rect id="rect124" height="30" width="60" y="0" x="0" fill="#006"/>
<g id="g584">
<path id="path146" d="m0 0v3.3541l53.292 26.646h6.708v-3.354l-53.292-26.646h-6.708zm60 0v3.354l-53.292 26.646h-6.708v-3.354l53.292-26.646h6.708z" fill="#fff"/>
<path id="path136" d="m25 0v30h10v-30h-10zm-25 10v10h60v-10h-60z" fill="#fff"/>
<path id="path141" d="m0 12v6h60v-6h-60zm27-12v30h6v-30h-6z" fill="#c00"/>
<path id="path150" d="m0 30 20-10h4.472l-20 10h-4.472zm0-30 20 10h-4.472l-15.528-7.7639v-2.2361zm35.528 10 20-10h4.472l-20 10h-4.472zm24.472 20-20-10h4.472l15.528 7.764v2.236z" fill="#c00"/>
</g>
</g>
</g>
</svg>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
<svg id="svg548" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="512" width="512" version="1" y="0" x="0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata id="metadata3809">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="flag" transform="matrix(1.0224 0 0 1.359 1.1963 3.9633)">
<g id="g554" stroke-width="1pt" fill-rule="evenodd">
<rect id="rect551" transform="matrix(1.0023 0 0 .51703 .0027402 -1.4085)" height="377.92" width="499.6" y="-2.9163" x="-1.17" fill="#e70011"/>
<rect id="rect553" transform="matrix(1.0023 0 0 .49845 .0027402 186.91)" height="377.92" width="499.6" y="-2.9163" x="-1.17" fill="#fff"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1006 B

View File

@ -834,6 +834,13 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@formatjs/ecma402-abstract@1.6.2":
version "1.6.2"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.6.2.tgz#9d064a2cf790769aa6721e074fb5d5c357084bb9"
integrity sha512-aLBODrSRhHaL/0WdQ0T2UsGqRbdtRRHqqrs4zwNQoRsGBEtEAvlj/rgr6Uea4PSymVJrbZBoAyECM2Z3Pq4i0g==
dependencies:
tslib "^2.1.0"
"@fullhuman/postcss-purgecss@^2.1.2":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz#50a954757ec78696615d3e118e3fee2d9291882e"
@ -904,6 +911,13 @@
globby "^11.0.1"
magic-string "^0.25.7"
"@rollup/plugin-json@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==
dependencies:
"@rollup/pluginutils" "^3.0.8"
"@rollup/plugin-node-resolve@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-10.0.0.tgz#44064a2b98df7530e66acf8941ff262fc9b4ead8"
@ -933,7 +947,7 @@
make-dir "^3.0.0"
mime "^2.4.4"
"@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.1.0":
"@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
@ -1420,6 +1434,11 @@ fast-glob@^3.1.1:
micromatch "^4.0.2"
picomatch "^2.2.1"
fast-memoize@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e"
integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==
fastq@^1.6.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481"
@ -1487,6 +1506,11 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globalyzer@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465"
integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==
globby@^11.0.1:
version "11.0.1"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357"
@ -1499,6 +1523,11 @@ globby@^11.0.1:
merge2 "^1.3.0"
slash "^3.0.0"
globrex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
@ -1599,6 +1628,23 @@ inherits@2:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
intl-messageformat-parser@6.4.2:
version "6.4.2"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.4.2.tgz#e2d28c3156c27961ead9d613ca55b6a155078d7d"
integrity sha512-IVNGy24lNEYr/KPWId5tF3KXRHFFbMgzIMI4kUonNa/ide2ywUYyBuOUro1IBGZJqjA2ncBVUyXdYKlMfzqpAA==
dependencies:
"@formatjs/ecma402-abstract" "1.6.2"
tslib "^2.1.0"
intl-messageformat@^9.3.15:
version "9.5.2"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.5.2.tgz#e72d32152c760b7411e413780e462909987c005a"
integrity sha512-sBGXcSQLyBuBA/kzAYhTpzhzkOGfSwGIau2W6FuwLZk0JE+VF3C+y0077FhVDOcRSi60iSfWzT8QC3Z7//dFxw==
dependencies:
fast-memoize "^2.5.2"
intl-messageformat-parser "6.4.2"
tslib "^2.1.0"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -1828,6 +1874,11 @@ minimist@^1.1.1, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mri@^1.1.0:
version "1.1.6"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -2227,6 +2278,13 @@ run-parallel@^1.1.9:
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
sade@^1.7.4:
version "1.7.4"
resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691"
integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==
dependencies:
mri "^1.1.0"
safe-buffer@5.1.2, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@ -2374,6 +2432,17 @@ supports-color@^7.0.0, supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
svelte-i18n@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/svelte-i18n/-/svelte-i18n-3.3.6.tgz#5d2a9f598b6e4999964afafad023c7f2f42c2e1f"
integrity sha512-HfXf2zPzVSuTRLXpfzMbAkE7C6v1JjHYhOaLM7e1Zr6w8l/9HpjDLVgd08ob0gwQqMDehJ630RVgcPOYJq46tQ==
dependencies:
deepmerge "^4.2.2"
estree-walker "^2.0.1"
intl-messageformat "^9.3.15"
sade "^1.7.4"
tiny-glob "^0.2.6"
svelte-preprocess@^4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.5.1.tgz#cfdb642b0305e09ff6090b99d6af30fbb29239e0"
@ -2431,6 +2500,14 @@ terser@^5.0.0:
source-map "~0.7.2"
source-map-support "~0.5.19"
tiny-glob@^0.2.6:
version "0.2.8"
resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.8.tgz#b2792c396cc62db891ffa161fe8b33e76123e531"
integrity sha512-vkQP7qOslq63XRX9kMswlby99kyO5OvKptw7AMwBVMjXEI7Tb61eoI5DydyEMOseyGS5anDN1VPoVxEvH01q8w==
dependencies:
globalyzer "0.1.0"
globrex "^0.1.2"
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@ -2455,6 +2532,11 @@ trouter@^3.1.0:
dependencies:
regexparam "^1.3.0"
tslib@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
uglify-js@^3.5.1:
version "3.12.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.4.tgz#93de48bb76bb3ec0fc36563f871ba46e2ee5c7ee"