Dev #5

Merged
Michilis merged 2 commits from dev into main 2026-08-24 20:36:42 +00:00
35 changed files with 657 additions and 145 deletions
Showing only changes of commit b95aab2bcd - Show all commits
+2 -2
View File
@@ -52,7 +52,7 @@ rating encoding, and the bugs this rebuild fixes.
## Requirements
- Node 20 or newer (uses `--experimental-strip-types`, so no build step for the API)
- Node 22.18 or newer (native TypeScript type stripping, so no build step for the API)
- pnpm 9 or newer
## Setup
@@ -759,7 +759,7 @@ Wants=network-online.target
Type=simple
User=cashumints
WorkingDirectory=/srv/cashumints/api
ExecStart=/usr/bin/node --experimental-strip-types src/index.ts
ExecStart=/usr/bin/node src/index.ts
Environment=NODE_ENV=production
Environment=PORT=8787
Environment=DB_PATH=/var/lib/cashumints/cashumints.db
+9 -9
View File
@@ -4,16 +4,16 @@
"private": true,
"type": "module",
"scripts": {
"dev": "node --env-file-if-exists=../.env --experimental-strip-types --watch src/index.ts",
"start": "node --env-file-if-exists=../.env --experimental-strip-types src/index.ts",
"seed": "node --env-file-if-exists=../.env --experimental-strip-types src/seed.ts",
"migrate": "node --env-file-if-exists=../.env --experimental-strip-types src/migrate.ts",
"dev": "node --env-file-if-exists=../.env --watch src/index.ts",
"start": "node --env-file-if-exists=../.env src/index.ts",
"seed": "node --env-file-if-exists=../.env src/seed.ts",
"migrate": "node --env-file-if-exists=../.env src/migrate.ts",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "node --env-file-if-exists=../.env --experimental-strip-types src/check.ts",
"test:warnings": "node --env-file-if-exists=../.env --experimental-strip-types src/check-warnings.ts",
"test:offline": "node --env-file-if-exists=../.env --experimental-strip-types src/check-offline.ts",
"test:lnurl": "node --env-file-if-exists=../.env --experimental-strip-types src/check-lnurl.ts",
"test:index": "node --experimental-strip-types src/check-index.ts"
"test": "node --env-file-if-exists=../.env src/check.ts",
"test:warnings": "node --env-file-if-exists=../.env src/check-warnings.ts",
"test:offline": "node --env-file-if-exists=../.env src/check-offline.ts",
"test:lnurl": "node --env-file-if-exists=../.env src/check-lnurl.ts",
"test:index": "node src/check-index.ts"
},
"dependencies": {
"@cashumints/shared": "workspace:*",
+1 -1
View File
@@ -4,7 +4,7 @@
"version": "2.0.0",
"type": "module",
"engines": {
"node": ">=20"
"node": ">=22.18"
},
"scripts": {
"dev": "pnpm --parallel --filter ./api --filter ./web dev",
+1 -1
View File
@@ -13,7 +13,7 @@
"bones": "node --env-file-if-exists=../.env scripts/bones.mjs",
"check:links": "node scripts/check-links.mjs dist",
"check:i18n": "node scripts/check-i18n.mjs",
"test": "node --experimental-strip-types --test test/*.test.mjs",
"test": "node --test test/*.test.mjs",
"check:hreflang": "node scripts/check-hreflang.mjs dist"
},
"dependencies": {
+2
View File
@@ -242,6 +242,8 @@ const DYNAMIC_PREFIXES = [
'fedimint.network.', // localNetworkName(network)
'feed.ecosystem.', // the /reviews filter, one option per ECOSYSTEMS entry
'reviews.dialog.bodyPlaceholder.', // one prompt per ecosystem, keyed by subject.type
'reviews.dialog.lede.', // the dialog's invitation, one per ecosystem
'reviews.rate.', // the reviews panel's inline "Rate this mint" row
];
const isDynamic = (key) => DYNAMIC_PREFIXES.some((prefix) => key.startsWith(prefix));
+27 -10
View File
@@ -37,16 +37,19 @@ const { locale } = pageLocale(Astro) as { locale: Locale };
const t = useI18n(locale);
/*
* The label, the placeholder and the review prompt all differ per ecosystem, and all
* three fall back to the Cashu wording for a type whose own string has not been written
* — the same rule the reviews panel already applies to its placeholder.
* The label, the invitation and the two review prompts all differ per ecosystem, and
* all of them fall back to the Cashu wording for a type whose own string has not been
* written — the same rule the reviews panel already applies to its placeholder.
*/
const wordsFor = (kind: IndexType): { label: string; placeholder: string; body: string } => {
const wordsFor = (
kind: IndexType,
): { label: string; placeholder: string; lede: string; body: string } => {
const key = (base: string): string =>
t.has(`${base}.${kind}`) ? `${base}.${kind}` : `${base}.cashu`;
return {
label: t(key('reviews.byUrl.label')),
placeholder: t(key('reviews.byUrl.placeholder')),
lede: t(key('reviews.dialog.lede')),
body: t(key('reviews.dialog.bodyPlaceholder')),
};
};
@@ -120,7 +123,7 @@ const initial = words[type] ?? words.cashu;
</form>
<div class="rbu-step" data-rbu-review hidden>
<WriteForm locale={locale} placeholder={initial.body} idPrefix="rbu">
<WriteForm locale={locale} lede={initial.lede} placeholder={initial.body} idPrefix="rbu">
<div class="modal-head" slot="head">
<h2 data-rbu-review-title>{t('reviews.byUrl.title')}</h2>
<button type="button" class="copy-btn" data-write-close aria-label={t('common.close')}>
@@ -199,6 +202,7 @@ const initial = words[type] ?? words.cashu;
const reviewTitle = dialog.querySelector<HTMLElement>('[data-rbu-review-title]')!;
const label = dialog.querySelector<HTMLElement>('[data-rbu-label]')!;
const ledeBox = dialog.querySelector<HTMLElement>('[data-write-lede]')!;
const bodyBox = dialog.querySelector<HTMLTextAreaElement>('textarea[name="content"]')!;
/**
@@ -208,11 +212,14 @@ const initial = words[type] ?? words.cashu;
* threw here would take the whole review flow down rather than showing a slightly
* generic label.
*/
const words = ((): Record<string, { label: string; placeholder: string; body: string }> => {
const words = ((): Record<
string,
{ label: string; placeholder: string; lede: string; body: string }
> => {
try {
return JSON.parse(dialog.dataset['rbuWords'] ?? '{}') as Record<
string,
{ label: string; placeholder: string; body: string }
{ label: string; placeholder: string; lede: string; body: string }
>;
} catch {
return {};
@@ -234,6 +241,7 @@ const initial = words[type] ?? words.cashu;
if (!wording) return;
label.textContent = wording.label;
input.placeholder = wording.placeholder;
ledeBox.textContent = wording.lede;
bodyBox.placeholder = wording.body;
}
@@ -268,7 +276,15 @@ const initial = words[type] ?? words.cashu;
/* ---------- moving between the steps ---------- */
function showStep(next: 'identify' | 'review'): void {
/**
* Cross-fade to the other step, and hand back the moment it is in the flow.
*
* `onShown` is where anything that focuses belongs: a hidden element cannot take
* focus, so setting up the incoming step before this callback runs would put the
* cursor on the document body and leave the reader pressing Tab to find the form.
* It runs before the pane is painted, so the step is never seen half-arranged.
*/
function showStep(next: 'identify' | 'review', onShown?: () => void): void {
const from = next === 'review' ? identify : reviewStep;
const to = next === 'review' ? reviewStep : identify;
const back = next === 'identify';
@@ -276,6 +292,7 @@ const initial = words[type] ?? words.cashu;
if (prefersReducedMotion()) {
from.hidden = true;
to.hidden = false;
onShown?.();
return;
}
@@ -286,6 +303,7 @@ const initial = words[type] ?? words.cashu;
to.hidden = false;
to.classList.add(back ? 'is-entering-back' : 'is-entering');
onShown?.();
// Two frames: one for the browser to lay the pane out hidden-to-shown, one for
// the transition to have a starting value to move from.
requestAnimationFrame(() =>
@@ -388,8 +406,7 @@ const initial = words[type] ?? words.cashu;
resolved = { mint: result.mint, subject };
drawPreview(result.mint);
writer.reset();
showStep('review');
writer.open();
showStep('review', () => writer.open());
}
identify.addEventListener('submit', (event) => {
+77 -12
View File
@@ -44,16 +44,19 @@ const f = formatters(t);
const dist = distribution;
/*
* The placeholder asks about the thing being reviewed, which is the one sentence in
* this panel that cannot be ecosystem-neutral without going vague: "how did minting and
* melting work out" means nothing about a federation, and "how did it go" means nothing
* about anything. One key per ecosystem, falling back to the Cashu wording for a type
* whose own has not been written yet.
* Three strings ask about the thing being reviewed, and none of them can be
* ecosystem-neutral without going vague: "how did minting and melting work out" means
* nothing about a federation, "rate this mint" is the wrong noun for one, and "how did
* it go" means nothing about anything. One key per ecosystem each, falling back to the
* Cashu wording for a type whose own has not been written yet.
*/
const placeholderKey = `reviews.dialog.bodyPlaceholder.${subject?.type ?? 'cashu'}`;
const bodyPlaceholder = t.has(placeholderKey)
? t(placeholderKey)
: t('reviews.dialog.bodyPlaceholder.cashu');
const forEcosystem = (base: string): string => {
const key = `${base}.${subject?.type ?? 'cashu'}`;
return t.has(key) ? t(key) : t(`${base}.cashu`);
};
const bodyPlaceholder = forEcosystem('reviews.dialog.bodyPlaceholder');
const dialogLede = forEcosystem('reviews.dialog.lede');
const rateLabel = forEcosystem('reviews.rate');
const critical = dist['1'] + dist['2'];
const fiveStar = dist['5'];
@@ -135,6 +138,26 @@ function escapeText(value: string): string {
</div>
</div>
{/*
The way in, where the reviews are.
The header button at the top of the page is a long way from here, and by the time
somebody has read what other people wrote they are at the other end of the panel.
This is the same flow, opened the same way — one `[data-write-open]` among several
— with one addition: pressing a star opens the dialog with that star already
chosen, so a rating takes exactly one press.
One button rather than five, because five buttons cannot live inside one and a row
that is only reachable star by star is a row a keyboard cannot use. The stars are
spans; which one was pressed is read off the click.
*/}
<button class="rate-row" type="button" data-write-open>
<span class="rate-row-label">{rateLabel}</span>
<span class="rate-row-stars" aria-hidden="true">
{[1, 2, 3, 4, 5].map((value) => <span class="rate-star" data-rate={value}>★</span>)}
</span>
</button>
{/* One line of consequence when the toggle above is on: how much is showing. */}
<p class="anon-note" data-anon-note hidden></p>
@@ -206,7 +229,7 @@ function escapeText(value: string): string {
every page load.
-->
<dialog class="modal write-dialog" data-write-dialog aria-labelledby="write-title">
<WriteForm locale={locale} placeholder={bodyPlaceholder}>
<WriteForm locale={locale} lede={dialogLede} placeholder={bodyPlaceholder}>
<div class="modal-head" slot="head">
{/* The mint's name is data, dropped into a translated sentence. */}
<h2 id="write-title">{t('reviews.dialog.title', { name })}</h2>
@@ -222,7 +245,44 @@ function escapeText(value: string): string {
<style>
/* The write dialog's own styles live in global.css beside the other modals: the
identity row and the status line are injected by the island, and Astro's scoped
selectors do not reach markup added through innerHTML. */
selectors do not reach markup added through innerHTML. The row below is in the
template, so it is styled here, where it is read. */
/*
* The inline way in. A row rather than a button-shaped button: it spans the panel
* the way the head above it does, so it reads as part of the reviews rather than as
* a second copy of the header's action.
*/
.rate-row {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
width: 100%; text-align: left; cursor: pointer;
padding: 12px 20px; border: none; border-bottom: 1px solid var(--line-soft);
background: none; color: var(--text); font-family: var(--body);
transition: background .15s;
}
.rate-row:hover { background: var(--card2); }
.rate-row:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
.rate-row-label { font-size: 13.5px; font-weight: 500; }
.rate-row-stars { display: flex; margin-left: auto; }
.rate-star {
font-size: 21px; line-height: 1; color: var(--line);
width: 30px; height: 30px;
display: inline-flex; align-items: center; justify-content: center;
transition: color .12s;
}
/* Fill the star under the pointer and every star before it, exactly as the picker in
the dialog does, so the row promises what the dialog then shows. */
.rate-row-stars:hover .rate-star:hover,
.rate-row-stars:hover .rate-star:has(~ .rate-star:hover) { color: var(--amber); }
/* Keyboard and touch never hover, and five grey stars read as switched off: the row
warms them when it has focus or under a thumb, without pretending a rating has
been chosen. */
.rate-row:focus-visible .rate-star,
.rate-row:active .rate-star { color: var(--amber-dim); }
@media (max-width: 480px) {
.rate-row { padding: 12px 16px; }
}
.load-row { padding: 16px 20px; display: flex; justify-content: center; border-top: 1px solid var(--line-soft); }
.pager { display: flex; gap: 6px; align-items: center; font-size: 13.5px; flex-wrap: wrap; justify-content: center; }
@@ -788,7 +848,12 @@ function escapeText(value: string): string {
}
for (const trigger of document.querySelectorAll('[data-write-open]')) {
trigger.addEventListener('click', () => writer.open());
trigger.addEventListener('click', (event) => {
// A star in the inline row carries the rating it stands for; every other
// trigger on the page is just "open it".
const star = (event.target as HTMLElement).closest<HTMLElement>('[data-rate]');
writer.open(star ? { rating: Number(star.dataset['rate']) } : {});
});
}
}
+47 -7
View File
@@ -1,22 +1,32 @@
---
/**
* The write-a-review form: stars, body, identity row, actions.
* The write-a-review form: a login gate, stars, body, identity row, actions.
*
* One markup, two dialogs. The reviews panel on a mint page opens it directly; the
* review-by-URL dialog on an index page opens it as its second step, once the reader's
* pasted address has been indexed. Its behaviour lives in `lib/write-review.ts`, and
* every hook below (`data-write-*`) is what that module binds to.
*
* Three states, only one of which is ever on screen:
*
* 1. **Gate.** No session and no NIP-07 extension: there is nothing to sign with, so
* the dialog asks for a login before it asks for anything else. Writing a review
* into a form that cannot publish it is the one outcome worth designing away.
* 2. **Stars.** The rating is the whole first step. It is the one thing every review
* must carry, it takes one tap, and it is what the rest of the form is *for*.
* 3. **The rest.** Body, identity and publish, revealed once a rating is chosen.
*
* Nothing here knows which ecosystem is being reviewed. The heading is a slot because
* the two dialogs have genuinely different heads — one names the mint, the other has a
* step back to the address field — and the placeholder is a prop for the same reason
* the panel already had one: "how did minting and melting work out" means nothing about
* a federation.
* step back to the address field — and the lede and placeholder are props for the same
* reason: "how did minting and melting work out" means nothing about a federation.
*/
import { useI18n, type Locale } from '../i18n';
interface Props {
locale: Locale;
/** The one line under the heading, chosen per ecosystem by the caller. */
lede: string;
/** The body placeholder, chosen per ecosystem by the caller. */
placeholder: string;
/**
@@ -29,7 +39,7 @@ interface Props {
idPrefix?: string;
}
const { locale, placeholder, idPrefix = 'review' } = Astro.props;
const { locale, lede, placeholder, idPrefix = 'review' } = Astro.props;
const t = useI18n(locale);
const bodyId = `${idPrefix}-body`;
@@ -39,10 +49,24 @@ const countId = `${idPrefix}-count`;
<form method="dialog" class="modal-body write-form" data-write-form novalidate>
<slot name="head" />
<p class="modal-note">{t('reviews.dialog.note')}</p>
<p class="modal-note" data-write-lede>{lede}</p>
<div class="write-id" data-write-identity></div>
{/*
The gate. Shown in place of everything below it when there is no signer at all, and
never on a page load: whether a reader has one is a browser fact the island checks
as the dialog opens. Its button is an ordinary `data-login-open`, so the same popup
the header opens is the one that opens here.
*/}
<div class="write-gate" data-write-gate hidden>
<p class="write-gate-title">{t('reviews.dialog.gate.title')}</p>
<p class="write-gate-lead">{t('reviews.dialog.gate.lead')}</p>
<div class="write-actions">
<button type="button" class="btn" data-write-close>{t('common.cancel')}</button>
<button type="button" class="btn primary" data-login-open>{t('login.open')}</button>
</div>
</div>
<div class="write-steps" data-write-steps>
<fieldset class="star-picker" data-star-picker>
<legend class="sr-only">{t('reviews.dialog.ratingLegend')}</legend>
{
@@ -54,11 +78,18 @@ const countId = `${idPrefix}-count`;
</label>
))
}
{/*
The only "Choose a rating" on screen. It used to be said here and again beside
the publish button, which is one prompt too many for one decision.
*/}
<span class="star-label" data-star-label aria-live="polite">
{t('reviews.dialog.chooseRating')}
</span>
</fieldset>
<div class="write-rest" data-write-rest hidden>
<div class="write-id" data-write-identity></div>
<label class="write-label" for={bodyId}>{t('reviews.dialog.bodyLabel')}</label>
<div class="write-box">
<textarea id={bodyId} name="content" rows="5" aria-describedby={countId} placeholder={placeholder}
@@ -79,4 +110,13 @@ const countId = `${idPrefix}-count`;
<span data-write-submit-label>{t('reviews.dialog.submit')}</span>
</button>
</div>
{/*
What pressing that button actually does, beside the button rather than in the
heading: it is a consequence, and a consequence belongs next to the thing that
causes it, not above a form nobody has filled in yet.
*/}
<p class="write-permanent">{t('reviews.dialog.permanent')}</p>
</div>
</div>
</form>
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "لا استعراضات بعد سيكون الأول ليكتابة واحدة.",
"reviews.emptyWritten": "لا استعراضات مكتوبة هنا بعد. سيكون الأول ليكتابة واحدة.",
"reviews.emptyFilter": "لا استعراضات تطابق ده المرشح",
"reviews.rate.cashu": "قيّم جهة الإصدار دي",
"reviews.rate.fedimint": "قيّم الاتحاد ده",
"reviews.rate.lnurl": "قيّم جهة الإصدار دي",
"reviews.noscript": "(الاستعراضات موقعة على (Nostr وتحمّل في مركبك لذا دي القائمة تحتاج (جافاسكريت فالتقديرات والعدادات الواردة أعلاه تأتي من الموقع نفسه.",
"reviews.relayError": "مينفعش أن تصل إلى النفقة. وقد تكون الاستعراضات غير كاملة.",
"reviews.srRating": "{rating} من 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} نجم واحد",
"reviews.breakdown.none": "{n} بدون تصنيف",
"reviews.dialog.title": "راجع {name}",
"reviews.dialog.note": "وقعت بمفتاح (Nostr) الخاص بك ونُشرت إلى المناورات العامة ومينفعش تحريرها أو حذفها.",
"reviews.dialog.lede.cashu": "احكيلنا تجربتك مع جهة الإصدار دي.",
"reviews.dialog.lede.fedimint": "احكيلنا تجربتك مع الاتحاد ده.",
"reviews.dialog.lede.lnurl": "احكيلنا تجربتك مع جهة الإصدار دي.",
"reviews.dialog.permanent": "بيتنشر بشكل دائم على موزّعات Nostr العامة.",
"reviews.dialog.gate.title": "سجّل الدخول عشان تكتب تقييم",
"reviews.dialog.gate.lead": "التقييمات بتتوقّع بمفتاح Nostr بتاعك. المفتاح بيفضل في المُوقِّع، والموقع ده عمره ما بيشوفه.",
"reviews.dialog.ratingLegend": "تقديرك",
"reviews.dialog.starsSr.one": "{n}",
"reviews.dialog.starsSr.other": "{n} نجوم",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "في انتظار توقيعك",
"reviews.dialog.confirmClose": "إغلاق ده الاستعراض؟ مشروعك محتفظ به حتى تغادر الموقع",
"reviews.dialog.why.login": "تسجيل في النشر",
"reviews.dialog.why.rating": "اختيار تقدير",
"reviews.dialog.why.words": "أكتب بضع كلمات",
"reviews.dialog.why.more.one": "{n} شخصية أخرى",
"reviews.dialog.why.more.other": "{n} المزيد من الشخصيات",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "لا توجد تعليقات حتى الآن. كن أول من يكتب تعليقًا.",
"reviews.emptyWritten": "لا توجد تعليقات مكتوبة هنا حتى الآن. كن أول من يكتب تعليقًا.",
"reviews.emptyFilter": "لا توجد تقييمات تتطابق مع هذا الفلتر.",
"reviews.rate.cashu": "قيّم جهة الإصدار هذه",
"reviews.rate.fedimint": "قيّم هذا الاتحاد",
"reviews.rate.lnurl": "قيّم جهة الإصدار هذه",
"reviews.noscript": "يتم تسجيل التعليقات على Nostr ويتم تحميلها في متصفحك، لذا تحتاج هذه القائمة إلى JavaScript. أما التقييمات وأعداد التعليقات المذكورة أعلاه فهي مستمدة من الموقع نفسه.",
"reviews.relayError": "تعذر الوصول إلى جهاز الترحيل. قد تكون المراجعات غير مكتملة.",
"reviews.srRating": "{rating} من أصل 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} نجمة واحدة",
"reviews.breakdown.none": "{n} بدون تقييم",
"reviews.dialog.title": "التقييم {name}",
"reviews.dialog.note": "تم التوقيع عليه باستخدام مفتاحك Nostr ونشره على الموزعات العامة. ولا يمكن تعديله أو حذفه.",
"reviews.dialog.lede.cashu": "شارك تجربتك مع جهة الإصدار هذه.",
"reviews.dialog.lede.fedimint": "شارك تجربتك مع هذا الاتحاد.",
"reviews.dialog.lede.lnurl": "شارك تجربتك مع جهة الإصدار هذه.",
"reviews.dialog.permanent": "يُنشر بشكل دائم على موزّعات Nostr العامة.",
"reviews.dialog.gate.title": "سجّل الدخول لكتابة تقييم",
"reviews.dialog.gate.lead": "تُوقَّع التقييمات بمفتاح Nostr الخاص بك. يبقى المفتاح في المُوقِّع، ولا يطّلع عليه هذا الموقع أبدًا.",
"reviews.dialog.ratingLegend": "تقييمك",
"reviews.dialog.starsSr.one": "{n} نجمة",
"reviews.dialog.starsSr.other": "{n} نجمة",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "في انتظار الموقّع",
"reviews.dialog.confirmClose": "هل تريد إغلاق هذه المراجعة؟ سيتم الاحتفاظ بمسودتك حتى تغادر الموقع.",
"reviews.dialog.why.login": "تسجيل الدخول للنشر",
"reviews.dialog.why.rating": "اختر تقييمًا",
"reviews.dialog.why.words": "اكتب بضع كلمات",
"reviews.dialog.why.more.one": "{n} حرف إضافي",
"reviews.dialog.why.more.other": "{n} حرف إضافي",
+9 -2
View File
@@ -279,6 +279,9 @@
"reviews.empty": "Ještě žádné recenze. Buďte první, kdo napsat jeden.",
"reviews.emptyWritten": "Už žádné písemné recenze. Buďte první, kdo napsat jeden.",
"reviews.emptyFilter": "Žádné recenze se neshodují s tímto filmem.",
"reviews.rate.cashu": "Ohodnoťte tento mint",
"reviews.rate.fedimint": "Ohodnoťte tuto federaci",
"reviews.rate.lnurl": "Ohodnoťte tento mint",
"reviews.noscript": "Recenze jsou podepsány na Nostr a načíst do browseru, takže tento seznam potřebuje JavaScript. Hodnocení a počty výše pocházejí z webu sám.",
"reviews.relayError": "Nelze dosáhnout relé. Recenze mohou být nedokončené.",
"reviews.srRating": "{rating}z 5",
@@ -309,7 +312,12 @@
"reviews.breakdown.1": "{n}jedna hvězda",
"reviews.breakdown.none": "{n}bez ratingu",
"reviews.dialog.title": "Přezkum{name}",
"reviews.dialog.note": "Podepsáno vaším klíčem Nostr a zveřejněno pro veřejné relé. Nelze jej editovat nebo smazat.",
"reviews.dialog.lede.cashu": "Podělte se o svou zkušenost s tímto mintem.",
"reviews.dialog.lede.fedimint": "Podělte se o svou zkušenost s touto federací.",
"reviews.dialog.lede.lnurl": "Podělte se o svou zkušenost s tímto mintem.",
"reviews.dialog.permanent": "Zveřejněno natrvalo na veřejných Nostr relayích.",
"reviews.dialog.gate.title": "Přihlaste se, abyste mohli napsat recenzi",
"reviews.dialog.gate.lead": "Recenze se podepisují vaším klíčem Nostr. Zůstává ve vašem signeru, tato stránka ho nikdy nevidí.",
"reviews.dialog.ratingLegend": "Vaše hodnocení",
"reviews.dialog.starsSr.one": "{n}hvězda",
"reviews.dialog.starsSr.other": "{n}hvězdy",
@@ -332,7 +340,6 @@
"reviews.dialog.waitingSigner": "Čekám na váš podpis.",
"reviews.dialog.confirmClose": "Váš návrh zůstane, dokud neopustíte místo.",
"reviews.dialog.why.login": "Přihlásit se k publikování",
"reviews.dialog.why.rating": "Vyberte si rating",
"reviews.dialog.why.words": "Napiš pár slov.",
"reviews.dialog.why.more.one": "{n}více charakteru",
"reviews.dialog.why.more.other": "{n}více znaků",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Der er endnu ingen anmeldelser. Vær den første til at skrive en.",
"reviews.emptyWritten": "Der er endnu ingen skriftlige anmeldelser her. Vær den første til at skrive en.",
"reviews.emptyFilter": "Der er ingen anmeldelser, der matcher dette filter.",
"reviews.rate.cashu": "Bedøm denne mint",
"reviews.rate.fedimint": "Bedøm denne føderation",
"reviews.rate.lnurl": "Bedøm denne mint",
"reviews.noscript": "Anmeldelserne er underskrevet med Nostr og indlæses i din browser, så denne liste kræver JavaScript. Bedømmelserne og antallet ovenfor stammer fra selve hjemmesiden.",
"reviews.relayError": "Det var ikke muligt at oprette forbindelse til relæet. Anmeldelserne kan være ufuldstændige.",
"reviews.srRating": "{rating} ud af 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} én stjerne",
"reviews.breakdown.none": "{n} uden bedømmelse",
"reviews.dialog.title": "Anmeldelse af {name}",
"reviews.dialog.note": "Underskrevet med din Nostr-nøgle og offentliggjort på offentlige relæer. Den kan hverken redigeres eller slettes.",
"reviews.dialog.lede.cashu": "Del din oplevelse med denne mint.",
"reviews.dialog.lede.fedimint": "Del din oplevelse med denne føderation.",
"reviews.dialog.lede.lnurl": "Del din oplevelse med denne mint.",
"reviews.dialog.permanent": "Offentliggøres permanent på offentlige Nostr-relays.",
"reviews.dialog.gate.title": "Log ind for at skrive en anmeldelse",
"reviews.dialog.gate.lead": "Anmeldelser signeres med din Nostr-nøgle. Den bliver i din signer, dette websted ser den aldrig.",
"reviews.dialog.ratingLegend": "Din bedømmelse",
"reviews.dialog.starsSr.one": "{n}-stjerne",
"reviews.dialog.starsSr.other": "{n}-stjerner",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Vi venter på din underskriver",
"reviews.dialog.confirmClose": "Vil du lukke denne anmeldelse? Dit udkast gemmes, indtil du forlader siden.",
"reviews.dialog.why.login": "Log ind for at offentliggøre",
"reviews.dialog.why.rating": "Vælg en bedømmelse",
"reviews.dialog.why.words": "Skriv et par ord",
"reviews.dialog.why.more.one": "{n} flere tegn",
"reviews.dialog.why.more.other": "{n} flere tegn",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Es gibt noch keine Bewertungen. Schreiben Sie die erste Bewertung.",
"reviews.emptyWritten": "Hier gibt es noch keine schriftlichen Bewertungen. Schreiben Sie die erste Bewertung.",
"reviews.emptyFilter": "Es gibt keine Bewertungen, die diesem Filter entsprechen.",
"reviews.rate.cashu": "Diese Mint bewerten",
"reviews.rate.fedimint": "Diese Föderation bewerten",
"reviews.rate.lnurl": "Diese Mint bewerten",
"reviews.noscript": "Die Bewertungen werden auf Nostr signiert und in Ihrem Browser geladen, daher benötigt diese Liste JavaScript. Die Bewertungen und Zahlen oben stammen von der Website selbst.",
"reviews.relayError": "Das Relais konnte nicht erreicht werden. Die Bewertungen sind möglicherweise unvollständig.",
"reviews.srRating": "{rating} von 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n}, ein Stern",
"reviews.breakdown.none": "{n}, ohne Bewertung",
"reviews.dialog.title": "Rezension: „{name}“",
"reviews.dialog.note": "Mit Ihrem Nostr-Schlüssel signiert und auf öffentlichen Relays veröffentlicht. Es kann weder bearbeitet noch gelöscht werden.",
"reviews.dialog.lede.cashu": "Teilen Sie Ihre Erfahrung mit dieser Mint.",
"reviews.dialog.lede.fedimint": "Teilen Sie Ihre Erfahrung mit dieser Föderation.",
"reviews.dialog.lede.lnurl": "Teilen Sie Ihre Erfahrung mit dieser Mint.",
"reviews.dialog.permanent": "Wird dauerhaft auf öffentlichen Nostr-Relays veröffentlicht.",
"reviews.dialog.gate.title": "Melden Sie sich an, um eine Bewertung zu schreiben",
"reviews.dialog.gate.lead": "Bewertungen werden mit Ihrem Nostr-Schlüssel signiert. Er bleibt in Ihrem Signierer, diese Website sieht ihn nie.",
"reviews.dialog.ratingLegend": "Ihre Bewertung",
"reviews.dialog.starsSr.one": "{n}-Star",
"reviews.dialog.starsSr.other": "{n}-Stars",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Warten auf Ihren Unterzeichner",
"reviews.dialog.confirmClose": "Diese Bewertung schließen? Ihr Entwurf wird gespeichert, bis Sie die Website verlassen.",
"reviews.dialog.why.login": "Anmelden, um zu veröffentlichen",
"reviews.dialog.why.rating": "Wählen Sie eine Bewertung aus",
"reviews.dialog.why.words": "Schreib ein paar Worte",
"reviews.dialog.why.more.one": "{n}t ein noch charakterstärkerer Charakter",
"reviews.dialog.why.more.other": "{n} weitere Zeichen",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Δεν υπάρχουν κριτικές ακόμα.",
"reviews.emptyWritten": "Δεν υπάρχουν γραπτές κριτικές εδώ ακόμα.",
"reviews.emptyFilter": "Καμία αξιολόγηση δεν ταιριάζει με αυτό το φίλτρο.",
"reviews.rate.cashu": "Βαθμολογήστε αυτό το mint",
"reviews.rate.fedimint": "Βαθμολογήστε αυτή την ομοσπονδία",
"reviews.rate.lnurl": "Βαθμολογήστε αυτό το mint",
"reviews.noscript": "Οι κριτικές είναι υπογεγραμμένες στο Nostr και φορτώνουν στο πρόγραμμα περιήγησης σας, οπότε αυτή η λίστα χρειάζεται JavaScript. Η αξιολόγηση και οι μετρήσεις παραπάνω προέρχονται από την ίδια την ιστοσελίδα.",
"reviews.relayError": "Δεν μπόρεσε να φτάσει στο ρελέ. Οι κριτικές μπορεί να είναι ελλιπείς.",
"reviews.srRating": "{rating} από 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} ένα αστέρι",
"reviews.breakdown.none": "{n} χωρίς βαθμολογία",
"reviews.dialog.title": "Επανάληψη{name}",
"reviews.dialog.note": "Υπογραφή με το πλήκτρο Nostr και δημοσίευση σε δημόσιες αναμεταδόσεις. Δεν μπορεί να επεξεργαστεί ή να διαγραφεί.",
"reviews.dialog.lede.cashu": "Μοιραστείτε την εμπειρία σας με αυτό το mint.",
"reviews.dialog.lede.fedimint": "Μοιραστείτε την εμπειρία σας με αυτή την ομοσπονδία.",
"reviews.dialog.lede.lnurl": "Μοιραστείτε την εμπειρία σας με αυτό το mint.",
"reviews.dialog.permanent": "Δημοσιεύεται μόνιμα σε δημόσια relays του Nostr.",
"reviews.dialog.gate.title": "Συνδεθείτε για να γράψετε μια κριτική",
"reviews.dialog.gate.lead": "Οι κριτικές υπογράφονται με το κλειδί σας Nostr. Παραμένει στον signer σας, αυτό το site δεν το βλέπει ποτέ.",
"reviews.dialog.ratingLegend": "Η βαθμολογία σας",
"reviews.dialog.starsSr.one": "{n} άστρο",
"reviews.dialog.starsSr.other": "{n} αστέρια",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Περιμένω τον υπογράφοντα σου.",
"reviews.dialog.confirmClose": "Κλείστε αυτή την κριτική; Το προσχέδιο σας διατηρείται μέχρι να φύγετε από το site.",
"reviews.dialog.why.login": "Σύνδεση για δημοσίευση",
"reviews.dialog.why.rating": "Επιλέξτε μια βαθμολογία",
"reviews.dialog.why.words": "Γράψε μερικές λέξεις.",
"reviews.dialog.why.more.one": "{n} περισσότερο χαρακτήρα",
"reviews.dialog.why.more.other": "{n} περισσότεροι χαρακτήρες",
+9 -2
View File
@@ -267,6 +267,9 @@
"reviews.empty": "No reviews yet. Be the first to write one.",
"reviews.emptyWritten": "No written reviews here yet. Be the first to write one.",
"reviews.emptyFilter": "No reviews match this filter.",
"reviews.rate.cashu": "Rate this mint",
"reviews.rate.fedimint": "Rate this federation",
"reviews.rate.lnurl": "Rate this mint",
"reviews.noscript": "Reviews are signed on Nostr and load in your browser, so this list needs JavaScript. The rating and counts above come from the site itself.",
"reviews.relayError": "Could not reach the relay. Reviews may be incomplete.",
"reviews.srRating": "{rating} out of 5",
@@ -293,7 +296,12 @@
"reviews.breakdown.1": "{n} one star",
"reviews.breakdown.none": "{n} with no rating",
"reviews.dialog.title": "Review {name}",
"reviews.dialog.note": "Signed with your Nostr key and published to public relays. It cannot be edited or deleted.",
"reviews.dialog.lede.cashu": "Share your experience with this mint.",
"reviews.dialog.lede.fedimint": "Share your experience with this federation.",
"reviews.dialog.lede.lnurl": "Share your experience with this mint.",
"reviews.dialog.permanent": "Published permanently to public Nostr relays.",
"reviews.dialog.gate.title": "Log in to write a review",
"reviews.dialog.gate.lead": "Reviews are signed with your Nostr key. It stays in your signer, this site never sees it.",
"reviews.dialog.ratingLegend": "Your rating",
"reviews.dialog.starsSr.one": "{n} star",
"reviews.dialog.starsSr.other": "{n} stars",
@@ -314,7 +322,6 @@
"reviews.dialog.waitingSigner": "Waiting for your signer",
"reviews.dialog.confirmClose": "Close this review? Your draft is kept until you leave the site.",
"reviews.dialog.why.login": "Log in to publish",
"reviews.dialog.why.rating": "Choose a rating",
"reviews.dialog.why.words": "Write a few words",
"reviews.dialog.why.more.one": "{n} more character",
"reviews.dialog.why.more.other": "{n} more characters",
+9 -2
View File
@@ -271,6 +271,9 @@
"reviews.empty": "Todavía no hay reseñas. Sé la primera persona en escribir una.",
"reviews.emptyWritten": "Todavía no hay reseñas escritas aquí. Sé quien escriba la primera.",
"reviews.emptyFilter": "Ninguna reseña coincide con este filtro.",
"reviews.rate.cashu": "Valora este mint",
"reviews.rate.fedimint": "Valora esta federación",
"reviews.rate.lnurl": "Valora este mint",
"reviews.noscript": "Las reseñas están firmadas en Nostr y se cargan en tu navegador, así que esta lista necesita JavaScript. La valoración y los recuentos de arriba vienen del propio sitio.",
"reviews.relayError": "No se ha podido llegar al relay. Puede que falten reseñas.",
"reviews.srRating": "{rating} sobre 5",
@@ -297,7 +300,12 @@
"reviews.breakdown.1": "{n} de una estrella",
"reviews.breakdown.none": "{n} sin valoración",
"reviews.dialog.title": "Reseñar {name}",
"reviews.dialog.note": "Se firma con tu clave Nostr y se publica en relays públicos. No se puede editar ni borrar.",
"reviews.dialog.lede.cashu": "Cuenta tu experiencia con este mint.",
"reviews.dialog.lede.fedimint": "Cuenta tu experiencia con esta federación.",
"reviews.dialog.lede.lnurl": "Cuenta tu experiencia con este mint.",
"reviews.dialog.permanent": "Se publica de forma permanente en relays públicos de Nostr.",
"reviews.dialog.gate.title": "Inicia sesión para escribir una reseña",
"reviews.dialog.gate.lead": "Las reseñas se firman con tu clave Nostr. Se queda en tu firmante, este sitio no la ve nunca.",
"reviews.dialog.ratingLegend": "Tu valoración",
"reviews.dialog.starsSr.one": "{n} estrella",
"reviews.dialog.starsSr.other": "{n} estrellas",
@@ -318,7 +326,6 @@
"reviews.dialog.waitingSigner": "Esperando a tu firmante",
"reviews.dialog.confirmClose": "¿Cerrar esta reseña? Tu borrador se guarda hasta que salgas del sitio.",
"reviews.dialog.why.login": "Inicia sesión para publicar",
"reviews.dialog.why.rating": "Elige una valoración",
"reviews.dialog.why.words": "Escribe unas palabras",
"reviews.dialog.why.more.one": "{n} carácter más",
"reviews.dialog.why.more.other": "{n} caracteres más",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Aucun avis pour l'instant. Soyez le premier à en écrire un.",
"reviews.emptyWritten": "Pas encore de critiques écrites ici. Soyez le premier à en écrire un.",
"reviews.emptyFilter": "Aucun avis ne correspond à ce filtre.",
"reviews.rate.cashu": "Évaluer ce mint",
"reviews.rate.fedimint": "Évaluer cette fédération",
"reviews.rate.lnurl": "Évaluer ce mint",
"reviews.noscript": "Les avis sont signés sur Nostr et chargés dans votre navigateur, cette liste nécessite donc JavaScript. La note et les décomptes ci-dessus proviennent du site lui-même.",
"reviews.relayError": "Impossible d'atteindre le relais. Les avis peuvent être incomplets.",
"reviews.srRating": "{rating} sur 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} une étoile",
"reviews.breakdown.none": "{n} sans note",
"reviews.dialog.title": "Avis {name}",
"reviews.dialog.note": "Signé avec votre clé Nostr et publié auprès des relais publics. Il ne peut pas être modifié ou supprimé.",
"reviews.dialog.lede.cashu": "Partagez votre expérience avec ce mint.",
"reviews.dialog.lede.fedimint": "Partagez votre expérience avec cette fédération.",
"reviews.dialog.lede.lnurl": "Partagez votre expérience avec ce mint.",
"reviews.dialog.permanent": "Publié de façon permanente sur des relais Nostr publics.",
"reviews.dialog.gate.title": "Connectez-vous pour écrire un avis",
"reviews.dialog.gate.lead": "Les avis sont signés avec votre clé Nostr. Elle reste dans votre signataire, ce site ne la voit jamais.",
"reviews.dialog.ratingLegend": "Votre note",
"reviews.dialog.starsSr.one": "Étoile {n}",
"reviews.dialog.starsSr.other": "Étoiles {n}",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "En attente de votre signataire",
"reviews.dialog.confirmClose": "Fermer cet avis ? Votre brouillon est conservé jusqu'à votre départ du site.",
"reviews.dialog.why.login": "Connectez-vous pour publier",
"reviews.dialog.why.rating": "Choisissez une note",
"reviews.dialog.why.words": "Écrivez quelques mots",
"reviews.dialog.why.more.one": "{n} plus de caractère",
"reviews.dialog.why.more.other": "{n} plus de caractères",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "अभी तक कोई समीक्षा नहीं है। पहली समीक्षा लिखें।",
"reviews.emptyWritten": "यहाँ अभी तक कोई लिखित समीक्षा नहीं है। पहली समीक्षा लिखें।",
"reviews.emptyFilter": "इस फ़िल्टर से कोई समीक्षा मेल नहीं खाती।",
"reviews.rate.cashu": "इस मिंट को रेटिंग दें",
"reviews.rate.fedimint": "इस फ़ेडरेशन को रेटिंग दें",
"reviews.rate.lnurl": "इस मिंट को रेटिंग दें",
"reviews.noscript": "समीक्षाएँ Nostr पर साइन की जाती हैं और आपके ब्राउज़र में लोड होती हैं, इसलिए इस सूची के लिए JavaScript की आवश्यकता है। ऊपर दी गई रेटिंग और संख्याएँ स्वयं साइट से ली गई हैं।",
"reviews.relayError": "रिले तक नहीं पहुँच सका। समीक्षाएँ अधूरी हो सकती हैं।",
"reviews.srRating": "5 में से {rating}",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} एक सितारा",
"reviews.breakdown.none": "{n} बिना रेटिंग के",
"reviews.dialog.title": "समीक्षा {name}",
"reviews.dialog.note": "आपकी Nostr कुंजी से हस्ताक्षरित और सार्वजनिक रिले पर प्रकाशित। इसे संपादित या हटाया नहीं जा सकता।",
"reviews.dialog.lede.cashu": "इस मिंट के बारे में अपना अनुभव साझा करें।",
"reviews.dialog.lede.fedimint": "इस फ़ेडरेशन के बारे में अपना अनुभव साझा करें।",
"reviews.dialog.lede.lnurl": "इस मिंट के बारे में अपना अनुभव साझा करें।",
"reviews.dialog.permanent": "सार्वजनिक Nostr रिले पर स्थायी रूप से प्रकाशित।",
"reviews.dialog.gate.title": "समीक्षा लिखने के लिए लॉग इन करें",
"reviews.dialog.gate.lead": "समीक्षाएँ आपकी Nostr कुंजी से हस्ताक्षरित होती हैं। कुंजी आपके हस्ताक्षरकर्ता में रहती है, यह साइट इसे कभी नहीं देखती।",
"reviews.dialog.ratingLegend": "आपकी रेटिंग",
"reviews.dialog.starsSr.one": "{n} सितारा",
"reviews.dialog.starsSr.other": "{n} सितारे",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "आपके हस्ताक्षरकर्ता की प्रतीक्षा",
"reviews.dialog.confirmClose": "इस समीक्षा को बंद करें? आपका मसौदा तब तक रखा जाता है जब तक आप साइट छोड़ते नहीं हैं।",
"reviews.dialog.why.login": "प्रकाशित करने के लिए लॉग इन करें",
"reviews.dialog.why.rating": "एक रेटिंग चुनें",
"reviews.dialog.why.words": "कुछ शब्द लिखें",
"reviews.dialog.why.more.one": "{n} और वर्ण",
"reviews.dialog.why.more.other": "{n} और अक्षर",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Belum ada ulasan. Jadilah orang pertama yang menulis.",
"reviews.emptyWritten": "Belum ada ulasan tertulis di sini. Jadilah orang pertama yang menulis.",
"reviews.emptyFilter": "Tidak ada ulasan yang cocok dengan filter ini.",
"reviews.rate.cashu": "Beri rating mint ini",
"reviews.rate.fedimint": "Beri rating federasi ini",
"reviews.rate.lnurl": "Beri rating mint ini",
"reviews.noscript": "Ulasan ditandatangani pada Nostr dan muat dalam peramban Anda, jadi daftar ini membutuhkan JavaScript. Rating dan jumlah di atas berasal dari situs itu sendiri.",
"reviews.relayError": "Tidak bisa mencapai relay. Ulasan mungkin tidak lengkap.",
"reviews.srRating": "{rating} dari 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} satu bintang",
"reviews.breakdown.none": "{n} tanpa rating",
"reviews.dialog.title": "Ulasan {name}",
"reviews.dialog.note": "Ditandatangani dengan kunci Nostr Anda dan diterbitkan ke relay publik. Tidak dapat disunting atau dihapus.",
"reviews.dialog.lede.cashu": "Bagikan pengalaman Anda dengan mint ini.",
"reviews.dialog.lede.fedimint": "Bagikan pengalaman Anda dengan federasi ini.",
"reviews.dialog.lede.lnurl": "Bagikan pengalaman Anda dengan mint ini.",
"reviews.dialog.permanent": "Diterbitkan secara permanen ke relay Nostr publik.",
"reviews.dialog.gate.title": "Masuk untuk menulis ulasan",
"reviews.dialog.gate.lead": "Ulasan ditandatangani dengan kunci Nostr Anda. Kunci tetap di penandatangan Anda, situs ini tidak pernah melihatnya.",
"reviews.dialog.ratingLegend": "Rating Anda",
"reviews.dialog.starsSr.one": "Bintang {n}",
"reviews.dialog.starsSr.other": "Bintang {n}",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Menunggu penandamu",
"reviews.dialog.confirmClose": "Tutup ulasan ini? Draf Anda disimpan sampai Anda meninggalkan situs.",
"reviews.dialog.why.login": "Log masuk untuk mempublikasikan",
"reviews.dialog.why.rating": "Pilih sebuah rating",
"reviews.dialog.why.words": "Tulis beberapa kata",
"reviews.dialog.why.more.one": "{n} lebih banyak karakter",
"reviews.dialog.why.more.other": "{n} lebih banyak karakter",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Ancora nessuna recensione. Sii il primo a scriverne uno.",
"reviews.emptyWritten": "Nessuna recensione scritta qui. Sii il primo a scriverne uno.",
"reviews.emptyFilter": "Nessuna recensione corrisponde a questo filtro.",
"reviews.rate.cashu": "Valuta questo mint",
"reviews.rate.fedimint": "Valuta questa federazione",
"reviews.rate.lnurl": "Valuta questo mint",
"reviews.noscript": "Le recensioni sono firmate su Nostr e caricare nel browser, quindi questa lista ha bisogno di JavaScript. La valutazione e conta sopra provengono dal sito stesso.",
"reviews.relayError": "Non poteva raggiungere il relè. Le recensioni possono essere incomplete.",
"reviews.srRating": "{rating} su 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} una stella",
"reviews.breakdown.none": "{n} senza classificazione",
"reviews.dialog.title": "Recensione {name}",
"reviews.dialog.note": "Firmato con la chiave Nostr e pubblicato su relè pubblici. Non può essere modificato o cancellato.",
"reviews.dialog.lede.cashu": "Racconta la tua esperienza con questo mint.",
"reviews.dialog.lede.fedimint": "Racconta la tua esperienza con questa federazione.",
"reviews.dialog.lede.lnurl": "Racconta la tua esperienza con questo mint.",
"reviews.dialog.permanent": "Pubblicato in modo permanente su relay Nostr pubblici.",
"reviews.dialog.gate.title": "Accedi per scrivere una recensione",
"reviews.dialog.gate.lead": "Le recensioni sono firmate con la tua chiave Nostr. Resta nel tuo signer, questo sito non la vede mai.",
"reviews.dialog.ratingLegend": "La tua valutazione",
"reviews.dialog.starsSr.one": "{n} stella stella",
"reviews.dialog.starsSr.other": "{n} stelle",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Aspettando il tuo firmatario",
"reviews.dialog.confirmClose": "Chiudi questa recensione? La tua bozza viene mantenuta fino a quando non esci dal sito.",
"reviews.dialog.why.login": "Accedi per pubblicare",
"reviews.dialog.why.rating": "Scegli una valutazione",
"reviews.dialog.why.words": "Scrivi qualche parola",
"reviews.dialog.why.more.one": "{n} più carattere",
"reviews.dialog.why.more.other": "{n} più caratteri",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "レビューはありません。 まずは書いてください。",
"reviews.emptyWritten": "まだレビューはありません。 まずは書いてください。",
"reviews.emptyFilter": "このフィルタに一致するレビューはありません。",
"reviews.rate.cashu": "この mint を評価する",
"reviews.rate.fedimint": "このフェデレーションを評価する",
"reviews.rate.lnurl": "この mint を評価する",
"reviews.noscript": "レビューはNostrに署名され、ブラウザに読み込まれるので、このリストにはJavaScriptが必要です。 上記の評価とカウントは、サイト自体から来ています。",
"reviews.relayError": "リレーに到達できませんでした。 レビューは不完全かもしれません。",
"reviews.srRating": "{rating} から 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} ワンスター",
"reviews.breakdown.none": "{n} なしの評価",
"reviews.dialog.title": "レビュー {name}",
"reviews.dialog.note": "Nostrキーに署名し、パブリックリレーに公開します。 編集や削除はできません。",
"reviews.dialog.lede.cashu": "この mint での体験を共有してください。",
"reviews.dialog.lede.fedimint": "このフェデレーションでの体験を共有してください。",
"reviews.dialog.lede.lnurl": "この mint での体験を共有してください。",
"reviews.dialog.permanent": "公開 Nostr リレーに恒久的に公開されます。",
"reviews.dialog.gate.title": "レビューを書くにはログインしてください",
"reviews.dialog.gate.lead": "レビューはあなたの Nostr 鍵で署名されます。鍵は署名アプリの中にとどまり、このサイトが見ることはありません。",
"reviews.dialog.ratingLegend": "あなたの評価",
"reviews.dialog.starsSr.one": "{n} スター",
"reviews.dialog.starsSr.other": "{n} つ星ホテル",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "あなたの署名者を待っています",
"reviews.dialog.confirmClose": "このレビューを閉じる? サイトを離れるまで、あなたのドラフトは保持されます。",
"reviews.dialog.why.login": "ログインして公開する",
"reviews.dialog.why.rating": "評価を選ぶ",
"reviews.dialog.why.words": "いくつかの単語を書く",
"reviews.dialog.why.more.one": "{n} キャラクター",
"reviews.dialog.why.more.other": "{n} より多くの文字",
+9 -2
View File
@@ -267,6 +267,9 @@
"reviews.empty": "Nog geen reviews. Schrijf als eerste er een.",
"reviews.emptyWritten": "Hier staan nog geen geschreven reviews. Wees de eerste die er een schrijft.",
"reviews.emptyFilter": "Geen enkele review past bij dit filter.",
"reviews.rate.cashu": "Beoordeel deze mint",
"reviews.rate.fedimint": "Beoordeel deze federatie",
"reviews.rate.lnurl": "Beoordeel deze mint",
"reviews.noscript": "Reviews worden op Nostr ondertekend en in je browser geladen, dus deze lijst heeft JavaScript nodig. De beoordeling en de aantallen hierboven komen van de site zelf.",
"reviews.relayError": "De relay was niet bereikbaar. De reviews zijn misschien niet compleet.",
"reviews.srRating": "{rating} van de 5",
@@ -293,7 +296,12 @@
"reviews.breakdown.1": "{n} van één ster",
"reviews.breakdown.none": "{n} zonder beoordeling",
"reviews.dialog.title": "{name} reviewen",
"reviews.dialog.note": "Wordt ondertekend met jouw Nostr-sleutel en gepubliceerd op publieke relays. Hij kan niet bewerkt of verwijderd worden.",
"reviews.dialog.lede.cashu": "Deel je ervaring met deze mint.",
"reviews.dialog.lede.fedimint": "Deel je ervaring met deze federatie.",
"reviews.dialog.lede.lnurl": "Deel je ervaring met deze mint.",
"reviews.dialog.permanent": "Wordt permanent gepubliceerd op publieke Nostr-relays.",
"reviews.dialog.gate.title": "Log in om een review te schrijven",
"reviews.dialog.gate.lead": "Reviews worden ondertekend met je Nostr-sleutel. Die blijft in je signer, deze site ziet hem nooit.",
"reviews.dialog.ratingLegend": "Jouw beoordeling",
"reviews.dialog.starsSr.one": "{n} ster",
"reviews.dialog.starsSr.other": "{n} sterren",
@@ -314,7 +322,6 @@
"reviews.dialog.waitingSigner": "Wachten op je signer",
"reviews.dialog.confirmClose": "Deze review sluiten? Je concept blijft bewaard tot je de site verlaat.",
"reviews.dialog.why.login": "Log in om te publiceren",
"reviews.dialog.why.rating": "Kies een beoordeling",
"reviews.dialog.why.words": "Schrijf een paar woorden",
"reviews.dialog.why.more.one": "nog {n} teken",
"reviews.dialog.why.more.other": "nog {n} tekens",
+9 -2
View File
@@ -279,6 +279,9 @@
"reviews.empty": "Jeszcze brak recenzji. Bądź pierwszym, który go napisze.",
"reviews.emptyWritten": "Na razie nie ma tu żadnych pisemnych recenzji. Bądź pierwszym, który go napisze.",
"reviews.emptyFilter": "Żadna recenzja nie pasuje do tego filtra.",
"reviews.rate.cashu": "Oceń ten mint",
"reviews.rate.fedimint": "Oceń tę federację",
"reviews.rate.lnurl": "Oceń ten mint",
"reviews.noscript": "Recenzje są zalogowane w Nostr i ładują się w przeglądarce, więc ta lista wymaga JavaScript. Ocena i liczby powyżej pochodzą z samej strony.",
"reviews.relayError": "Nie udało się dołączyć do przekaźnika. Recenzje mogą być niekompletne.",
"reviews.srRating": "{rating} na 5",
@@ -309,7 +312,12 @@
"reviews.breakdown.1": "{n} jedną gwiazdką",
"reviews.breakdown.none": "{n} bez oceny",
"reviews.dialog.title": "Recenzja {name}",
"reviews.dialog.note": "Podpisany kluczem Nostr i opublikowany w publicznych przekaźnikach. Nie można go edytować ani usuwać.",
"reviews.dialog.lede.cashu": "Podziel się swoim doświadczeniem z tym mintem.",
"reviews.dialog.lede.fedimint": "Podziel się swoim doświadczeniem z tą federacją.",
"reviews.dialog.lede.lnurl": "Podziel się swoim doświadczeniem z tym mintem.",
"reviews.dialog.permanent": "Publikowane na stałe w publicznych relayach Nostr.",
"reviews.dialog.gate.title": "Zaloguj się, aby napisać recenzję",
"reviews.dialog.gate.lead": "Recenzje są podpisywane twoim kluczem Nostr. Zostaje on w twoim signerze, ta strona nigdy go nie widzi.",
"reviews.dialog.ratingLegend": "Twoja ocena",
"reviews.dialog.starsSr.one": "{n} gwiazda",
"reviews.dialog.starsSr.few": "{n} gwiazdki",
@@ -332,7 +340,6 @@
"reviews.dialog.waitingSigner": "Czekam na swojego podpisującego",
"reviews.dialog.confirmClose": "Zamknąć tę recenzję? Twój szkic jest przechowywany do momentu opuszczenia strony.",
"reviews.dialog.why.login": "Zaloguj się, aby opublikować",
"reviews.dialog.why.rating": "Wybierz ocenę",
"reviews.dialog.why.words": "Napisz kilka słów",
"reviews.dialog.why.more.one": "{n} więcej charakteru",
"reviews.dialog.why.more.few": "{n} kolejne postacie",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Ainda não há comentários. Seja o primeiro a escrever um.",
"reviews.emptyWritten": "Ainda não há comentários escritos aqui. Seja o primeiro a escrever um.",
"reviews.emptyFilter": "Nenhuma avaliação corresponde a este filtro.",
"reviews.rate.cashu": "Avalie este mint",
"reviews.rate.fedimint": "Avalie esta federação",
"reviews.rate.lnurl": "Avalie este mint",
"reviews.noscript": "As avaliações são assinadas em Nostr e carregadas em seu navegador, portanto, esta lista precisa de JavaScript. A classificação e as contagens acima vêm do próprio site.",
"reviews.relayError": "Não foi possível alcançar o relé. As avaliações podem estar incompletas.",
"reviews.srRating": "{rating} de 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} uma estrela",
"reviews.breakdown.none": "{n} sem classificação",
"reviews.dialog.title": "Revisão {name}",
"reviews.dialog.note": "Assinado com sua chave Nostr e publicado em retransmissores públicos. Não pode ser editado ou excluído.",
"reviews.dialog.lede.cashu": "Compartilhe sua experiência com este mint.",
"reviews.dialog.lede.fedimint": "Compartilhe sua experiência com esta federação.",
"reviews.dialog.lede.lnurl": "Compartilhe sua experiência com este mint.",
"reviews.dialog.permanent": "Publicado permanentemente em relays públicos do Nostr.",
"reviews.dialog.gate.title": "Faça login para escrever um comentário",
"reviews.dialog.gate.lead": "Os comentários são assinados com sua chave Nostr. Ela permanece no seu assinante, este site nunca a vê.",
"reviews.dialog.ratingLegend": "Sua classificação",
"reviews.dialog.starsSr.one": "Estrela {n}",
"reviews.dialog.starsSr.other": "Estrelas {n}",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Esperando pelo seu signatário",
"reviews.dialog.confirmClose": "Fechar esta avaliação? Seu rascunho é mantido até você sair do site.",
"reviews.dialog.why.login": "Faça login para publicar",
"reviews.dialog.why.rating": "Escolha uma classificação",
"reviews.dialog.why.words": "Escreva algumas palavras",
"reviews.dialog.why.more.one": "{n} mais caráter",
"reviews.dialog.why.more.other": "{n} mais caracteres",
+9 -2
View File
@@ -271,6 +271,9 @@
"reviews.empty": "Fii primul care scrie una.",
"reviews.emptyWritten": "Nu scriem recenzii aici încă.",
"reviews.emptyFilter": "Nici o recenzie nu se potriveşte cu acest filtru.",
"reviews.rate.cashu": "Evaluează acest mint",
"reviews.rate.fedimint": "Evaluează această federație",
"reviews.rate.lnurl": "Evaluează acest mint",
"reviews.noscript": "Recenziile sunt semnate pe Nostr și încărcați în browser-ul dvs., astfel încât această listă are nevoie de JavaScript. Evaluarea și numărul de mai sus provin de la site-ul în sine.",
"reviews.relayError": "Nu am putut ajunge la releu. Recenziile pot fi incomplete.",
"reviews.srRating": "{rating} din 5",
@@ -299,7 +302,12 @@
"reviews.breakdown.1": "{n}o stea",
"reviews.breakdown.none": "{n} fără rating",
"reviews.dialog.title": "Reexaminare{name}",
"reviews.dialog.note": "Semnat cu tasta Nostr și publicat la relee publice. Nu poate fi editat sau șters.",
"reviews.dialog.lede.cashu": "Împărtășește experiența ta cu acest mint.",
"reviews.dialog.lede.fedimint": "Împărtășește experiența ta cu această federație.",
"reviews.dialog.lede.lnurl": "Împărtășește experiența ta cu acest mint.",
"reviews.dialog.permanent": "Publicat permanent pe relayuri Nostr publice.",
"reviews.dialog.gate.title": "Autentifică-te pentru a scrie un comentariu",
"reviews.dialog.gate.lead": "Recenziile sunt semnate cu cheia ta Nostr. Rămâne în signerul tău, acest site nu o vede niciodată.",
"reviews.dialog.ratingLegend": "Evaluarea dvs.",
"reviews.dialog.starsSr.one": "{n} stea",
"reviews.dialog.starsSr.other": "{n} stele",
@@ -321,7 +329,6 @@
"reviews.dialog.waitingSigner": "Aştept semnalul tău.",
"reviews.dialog.confirmClose": "Ţi-ai păstrat proiectul până când ai plecat de pe site.",
"reviews.dialog.why.login": "Autentifică-te pentru publicare",
"reviews.dialog.why.rating": "Alegeți un rating",
"reviews.dialog.why.words": "Scrie câteva cuvinte.",
"reviews.dialog.why.more.one": "{n} mai mult caracter",
"reviews.dialog.why.more.other": "{n} mai multe caractere",
+9 -2
View File
@@ -279,6 +279,9 @@
"reviews.empty": "Отзывов пока нет. Будьте первым, кто оставит отзыв.",
"reviews.emptyWritten": "Здесь пока нет письменных отзывов. Будьте первым, кто оставит отзыв.",
"reviews.emptyFilter": "По данному фильтру отзывов не найдено.",
"reviews.rate.cashu": "Оцените этот минт",
"reviews.rate.fedimint": "Оцените эту федерацию",
"reviews.rate.lnurl": "Оцените этот минт",
"reviews.noscript": "Отзывы на Nostr подписываются и загружаются в браузере, поэтому для просмотра этого списка требуется JavaScript. Оценки и количество просмотров, указанные выше, взяты с самого сайта.",
"reviews.relayError": "Не удалось подключиться к relay. Часть отзывов может отсутствовать.",
"reviews.srRating": "{rating} из 5",
@@ -309,7 +312,12 @@
"reviews.breakdown.1": "{n}, одна звезда",
"reviews.breakdown.none": "{n}, без оценки",
"reviews.dialog.title": "Отзыв о {name}",
"reviews.dialog.note": "Отзыв будет подписан вашим ключом Nostr и опубликован на публичных relays. Его нельзя отредактировать или удалить.",
"reviews.dialog.lede.cashu": "Расскажите о своём опыте с этим минтом.",
"reviews.dialog.lede.fedimint": "Расскажите о своём опыте с этой федерацией.",
"reviews.dialog.lede.lnurl": "Расскажите о своём опыте с этим минтом.",
"reviews.dialog.permanent": "Публикуется навсегда на публичных relays Nostr.",
"reviews.dialog.gate.title": "Войдите, чтобы написать отзыв",
"reviews.dialog.gate.lead": "Отзывы подписываются вашим ключом Nostr. Он остаётся в вашем signer, сайт никогда его не видит.",
"reviews.dialog.ratingLegend": "Ваша оценка",
"reviews.dialog.starsSr.one": "{n} звезда",
"reviews.dialog.starsSr.other": "{n} звезд",
@@ -332,7 +340,6 @@
"reviews.dialog.waitingSigner": "Ожидание signer",
"reviews.dialog.confirmClose": "Закрыть этот отзыв? Ваш черновик сохранится до тех пор, пока вы не покинете сайт.",
"reviews.dialog.why.login": "Войдите в систему, чтобы опубликовать",
"reviews.dialog.why.rating": "Выберите оценку",
"reviews.dialog.why.words": "Напишите несколько слов",
"reviews.dialog.why.more.one": "нужен еще {n} символ",
"reviews.dialog.why.more.other": "{n} еще символов",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Inga recensioner ännu. Bli den första att skriva en.",
"reviews.emptyWritten": "Det finns inga skriftliga recensioner här än. Bli den första att skriva en.",
"reviews.emptyFilter": "Det finns inga recensioner som stämmer överens med detta filter.",
"reviews.rate.cashu": "Betygsätt denna mint",
"reviews.rate.fedimint": "Betygsätt denna federation",
"reviews.rate.lnurl": "Betygsätt denna mint",
"reviews.noscript": "Recensionerna är signerade med Nostr och laddas i din webbläsare, så den här listan kräver JavaScript. Betygen och antalet nedan hämtas direkt från webbplatsen.",
"reviews.relayError": "Det gick inte att nå reläet. Recensionerna kan vara ofullständiga.",
"reviews.srRating": "{rating} av 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} en stjärna",
"reviews.breakdown.none": "{n} utan betyg",
"reviews.dialog.title": "Recension av {name}",
"reviews.dialog.note": "Signerad med din Nostr-nyckel och publicerad på offentliga reläer. Den kan varken redigeras eller raderas.",
"reviews.dialog.lede.cashu": "Dela din upplevelse av denna mint.",
"reviews.dialog.lede.fedimint": "Dela din upplevelse av denna federation.",
"reviews.dialog.lede.lnurl": "Dela din upplevelse av denna mint.",
"reviews.dialog.permanent": "Publiceras permanent på publika Nostr-relayer.",
"reviews.dialog.gate.title": "Logga in för att skriva en recension",
"reviews.dialog.gate.lead": "Recensioner signeras med din Nostr-nyckel. Den stannar i din signer, den här webbplatsen ser den aldrig.",
"reviews.dialog.ratingLegend": "Ditt betyg",
"reviews.dialog.starsSr.one": "{n}-stjärna",
"reviews.dialog.starsSr.other": "{n} stjärnor",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Väntar på din undertecknare",
"reviews.dialog.confirmClose": "Vill du stänga den här recensionen? Ditt utkast sparas tills du lämnar webbplatsen.",
"reviews.dialog.why.login": "Logga in för att publicera",
"reviews.dialog.why.rating": "Välj ett betyg",
"reviews.dialog.why.words": "Skriv några ord",
"reviews.dialog.why.more.one": "{n} fler tecken",
"reviews.dialog.why.more.other": "{n} fler tecken",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Henüz inceleme yok. İlk yazan kişi ol.",
"reviews.emptyWritten": "Henüz yazılı bir inceleme yok. İlk yazan kişi ol.",
"reviews.emptyFilter": "Bu filtreyle eşleşen hiçbir yorum yok.",
"reviews.rate.cashu": "Bu mint'i puanla",
"reviews.rate.fedimint": "Bu federasyonu puanla",
"reviews.rate.lnurl": "Bu mint'i puanla",
"reviews.noscript": "İncelemeler Nostr'da imzalanır ve tarayıcınızda yüklenir, bu yüzden bu liste JavaScript gerektirir. Yukarıdaki puan ve sayımlar sitenin kendisinden geliyor.",
"reviews.relayError": "Röleye ulaşamadım. İncelemeler eksik olabilir.",
"reviews.srRating": "{rating} 5 üzerinden",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} bir yıldız",
"reviews.breakdown.none": "{n} puanı olmayan",
"reviews.dialog.title": "İnceleme {name}",
"reviews.dialog.note": "Nostr anahtarınızla imzalandı ve halka aktarıldı. Düzenlenemiyor veya silinmiyor.",
"reviews.dialog.lede.cashu": "Bu mint ile deneyiminizi paylaşın.",
"reviews.dialog.lede.fedimint": "Bu federasyon ile deneyiminizi paylaşın.",
"reviews.dialog.lede.lnurl": "Bu mint ile deneyiminizi paylaşın.",
"reviews.dialog.permanent": "Herkese açık Nostr relay'lerinde kalıcı olarak yayınlanır.",
"reviews.dialog.gate.title": "İnceleme yazmak için giriş yapın",
"reviews.dialog.gate.lead": "İncelemeler Nostr anahtarınızla imzalanır. Anahtar imzalayıcınızda kalır, bu site onu asla görmez.",
"reviews.dialog.ratingLegend": "Puanınız",
"reviews.dialog.starsSr.one": "{n} yıldızı",
"reviews.dialog.starsSr.other": "{n} yıldızlar",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "İmzalanını bekliyorum",
"reviews.dialog.confirmClose": "Bu incelemeyi kapatmak mı? Taslağınız site terk edene kadar saklanır.",
"reviews.dialog.why.login": "Yayınlamak için giriş yapın",
"reviews.dialog.why.rating": "Bir puan seçin",
"reviews.dialog.why.words": "Birkaç kelime yaz",
"reviews.dialog.why.more.one": "{n} daha karakter",
"reviews.dialog.why.more.other": "{n} daha fazla karakter",
+9 -2
View File
@@ -279,6 +279,9 @@
"reviews.empty": "Поки що відгуків немає. Будьте першими, хто напише такий.",
"reviews.emptyWritten": "Поки що немає письмових відгуків. Будьте першими, хто напише такий.",
"reviews.emptyFilter": "Жоден відгук не відповідає цьому фільтру.",
"reviews.rate.cashu": "Оцініть цей мінт",
"reviews.rate.fedimint": "Оцініть цю федерацію",
"reviews.rate.lnurl": "Оцініть цей мінт",
"reviews.noscript": "Відгуки підписуються на Nostr і завантажуються у вашому браузері, тому цей список потребує JavaScript. Вищезазначений рейтинг і підрахунок походять із самого сайту.",
"reviews.relayError": "Не вдалося підключитися до relay. Відгуки можуть бути неповними.",
"reviews.srRating": "{rating} з 5",
@@ -309,7 +312,12 @@
"reviews.breakdown.1": "{n} одну зірку",
"reviews.breakdown.none": "{n} без рейтингу",
"reviews.dialog.title": "Відгук про {name}",
"reviews.dialog.note": "Підписується вашим Nostr-ключем і публікується на загальнодоступних relays. Відгук не можна відредагувати або видалити.",
"reviews.dialog.lede.cashu": "Розкажіть про свій досвід із цим мінтом.",
"reviews.dialog.lede.fedimint": "Розкажіть про свій досвід із цією федерацією.",
"reviews.dialog.lede.lnurl": "Розкажіть про свій досвід із цим мінтом.",
"reviews.dialog.permanent": "Публікується назавжди на загальнодоступних relays Nostr.",
"reviews.dialog.gate.title": "Увійдіть, щоб написати відгук",
"reviews.dialog.gate.lead": "Відгуки підписуються вашим ключем Nostr. Він залишається у вашому signer, сайт ніколи його не бачить.",
"reviews.dialog.ratingLegend": "Ваш рейтинг",
"reviews.dialog.starsSr.one": "{n} зірка",
"reviews.dialog.starsSr.few": "{n} зірки",
@@ -332,7 +340,6 @@
"reviews.dialog.waitingSigner": "Очікування signer",
"reviews.dialog.confirmClose": "Закрити цей відгук? Чернетка зберігатиметься, доки ви не залишите сайт.",
"reviews.dialog.why.login": "Увійти для публікації",
"reviews.dialog.why.rating": "Виберіть рейтинг",
"reviews.dialog.why.words": "Напишіть кілька слів",
"reviews.dialog.why.more.one": "Ще {n} символ",
"reviews.dialog.why.more.few": "Ще {n} символи",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "ابھی تک کوئی تجزیے نہیں کیے۔ سب سے پہلے ایک لکھنے والے کو لکھیں۔",
"reviews.emptyWritten": "یہاں ابھی تک کوئی تحریری تجزیے نہیں لکھے۔ سب سے پہلے ایک لکھیں۔",
"reviews.emptyFilter": "کوئی تجزیے اس فیلڈر سے مل کر نہیں ملتے.",
"reviews.rate.cashu": "اس منٹ کو ریٹنگ دیں",
"reviews.rate.fedimint": "اس فیڈریشن کو ریٹنگ دیں",
"reviews.rate.lnurl": "اس منٹ کو ریٹنگ دیں",
"reviews.noscript": "آپ کے براؤزر میں نوسٹر اور لوڈ پر Reviews دستخط کیے گئے ہیں، چنانچہ اس فہرست میں جاوا ایسکریپٹ کی ضرورت ہوتی ہے. شرح خواندگی اور حساب سے اوپر آتا ہے خود سائٹ سے۔",
"reviews.relayError": "ریلیز تک رسائی حاصل نہیں ہو سکی۔ دوبارہ تجدید ہو سکتی ہے۔",
"reviews.srRating": "{rating}5 میں سے",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n}ایک ستارہ",
"reviews.breakdown.none": "{n}اور نہ ہی (اس میں) کمی ہوتی ہے۔",
"reviews.dialog.title": "اعادہ{name}",
"reviews.dialog.note": "آپ کی نوسٹر کلید کے ساتھ دستخط کیے اور عوامی ریلیز کے لیے شائع کیے۔اس میں ترمیم یا حذف نہیں کی جا سکتی۔",
"reviews.dialog.lede.cashu": "اس منٹ کے بارے میں اپنا تجربہ بتائیں۔",
"reviews.dialog.lede.fedimint": "اس فیڈریشن کے بارے میں اپنا تجربہ بتائیں۔",
"reviews.dialog.lede.lnurl": "اس منٹ کے بارے میں اپنا تجربہ بتائیں۔",
"reviews.dialog.permanent": "عوامی Nostr ریلیز پر مستقل طور پر شائع ہوتا ہے۔",
"reviews.dialog.gate.title": "جائزہ لکھنے کے لیے لاگ ان کریں",
"reviews.dialog.gate.lead": "جائزے آپ کی Nostr کلید سے دستخط ہوتے ہیں۔ کلید آپ کے سائنر میں رہتی ہے، یہ سائٹ اسے کبھی نہیں دیکھتی۔",
"reviews.dialog.ratingLegend": "آپ کی شرح",
"reviews.dialog.starsSr.one": "{n}ستارہ",
"reviews.dialog.starsSr.other": "{n}ستاروں",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "آپ کے دستخط کا انتظار",
"reviews.dialog.confirmClose": "اِس جائزے کو بند کریں ؟ آپ کا ڈرافٹ اُس وقت تک برقرار رہتا ہے جب تک آپ ویب‌سائٹ سے باہر نہیں جاتے ۔",
"reviews.dialog.why.login": "شائع کرنے کے لیے لاگ ان",
"reviews.dialog.why.rating": "فہرست منتخب کریں",
"reviews.dialog.why.words": "چند الفاظ لکھے ہیں۔",
"reviews.dialog.why.more.one": "{n}مزید حروف",
"reviews.dialog.why.more.other": "{n}مزید حروف",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "Vẫn chưa có đánh giá. Là người đầu tiên viết ra.",
"reviews.emptyWritten": "Chưa có bài phê bình nào ở đây. Là người đầu tiên viết ra.",
"reviews.emptyFilter": "Không có đánh giá khớp với bộ lọc này.",
"reviews.rate.cashu": "Đánh giá mint này",
"reviews.rate.fedimint": "Đánh giá liên minh này",
"reviews.rate.lnurl": "Đánh giá mint này",
"reviews.noscript": "Các bản ôn lại đã được ký trên Nostr và tải lên trình duyệt của bạn, vì vậy danh sách này cần JavaScript. Điểm đánh giá và điểm số ở trên đến từ chính trang web.",
"reviews.relayError": "Không thể tới phần tiếp theo. Xem lại có thể chưa đầy đủ.",
"reviews.srRating": "{rating} 9182741 trong số 5",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} Một ngôi sao",
"reviews.breakdown.none": "{n} mà không đánh giá",
"reviews.dialog.title": "Xem lại {name}",
"reviews.dialog.note": "Ký với chìa khóa mũi và được xuất bản để tiếp âm công cộng. Không thể sửa hay xoá nó.",
"reviews.dialog.lede.cashu": "Chia sẻ trải nghiệm của bạn với mint này.",
"reviews.dialog.lede.fedimint": "Chia sẻ trải nghiệm của bạn với liên minh này.",
"reviews.dialog.lede.lnurl": "Chia sẻ trải nghiệm của bạn với mint này.",
"reviews.dialog.permanent": "Được đăng vĩnh viễn lên các relay Nostr công khai.",
"reviews.dialog.gate.title": "Đăng nhập để viết bài đánh giá",
"reviews.dialog.gate.lead": "Bài đánh giá được ký bằng khoá Nostr của bạn. Khoá luôn nằm trong trình ký, trang web này không bao giờ thấy nó.",
"reviews.dialog.ratingLegend": "Đánh giá của bạn",
"reviews.dialog.starsSr.one": "{n} sao",
"reviews.dialog.starsSr.other": "{n} sao",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "Đang đợi người ký",
"reviews.dialog.confirmClose": "Đóng bài phê bình này? Bản nháp của anh được giữ lại cho đến khi anh rời khỏi trang web.",
"reviews.dialog.why.login": "Đăng nhập để xuất bản",
"reviews.dialog.why.rating": "Chọn đánh giá",
"reviews.dialog.why.words": "Viết vài từ",
"reviews.dialog.why.more.one": "{n} nhân vật",
"reviews.dialog.why.more.other": "{n} ký tự",
+9 -2
View File
@@ -263,6 +263,9 @@
"reviews.empty": "目前尚无评论。快来成为首位评论者吧。",
"reviews.emptyWritten": "这里还没有书面评论。快来成为第一个发表评论的人吧。",
"reviews.emptyFilter": "没有符合此筛选条件的评论。",
"reviews.rate.cashu": "为该铸币服务评分",
"reviews.rate.fedimint": "为该联邦评分",
"reviews.rate.lnurl": "为该铸币服务评分",
"reviews.noscript": "评论是在 Nostr 上签署的,并在您的浏览器中加载,因此此列表需要 JavaScript。上方的评分和数量来自该网站本身。",
"reviews.relayError": "无法连接到中继服务器。评论可能不完整。",
"reviews.srRating": "{rating}(满分5分)",
@@ -289,7 +292,12 @@
"reviews.breakdown.1": "{n} 一星",
"reviews.breakdown.none": "{n},无评分",
"reviews.dialog.title": "评论 {name}",
"reviews.dialog.note": "已使用您的 Nostr 密钥签名,并发布到公共中继节点。该条目无法编辑或删除。",
"reviews.dialog.lede.cashu": "分享你在该铸币服务的使用体验。",
"reviews.dialog.lede.fedimint": "分享你在该联邦的使用体验。",
"reviews.dialog.lede.lnurl": "分享你在该铸币服务的使用体验。",
"reviews.dialog.permanent": "将永久发布到公共 Nostr 中继节点。",
"reviews.dialog.gate.title": "登录后即可撰写评论",
"reviews.dialog.gate.lead": "评论使用您的 Nostr 密钥签名。密钥始终保存在您的签名器中,本网站绝不会看到它。",
"reviews.dialog.ratingLegend": "您的评分",
"reviews.dialog.starsSr.one": "{n} 星",
"reviews.dialog.starsSr.other": "{n} 星",
@@ -310,7 +318,6 @@
"reviews.dialog.waitingSigner": "等待您的签字人",
"reviews.dialog.confirmClose": "关闭此评论?您的草稿将保留至您离开本网站为止。",
"reviews.dialog.why.login": "登录以发布",
"reviews.dialog.why.rating": "选择评分",
"reviews.dialog.why.words": "写几句话",
"reviews.dialog.why.more.one": "{n} 更多字符",
"reviews.dialog.why.more.other": "{n} 更多字符",
+198 -17
View File
@@ -18,6 +18,11 @@
* The one behavioural difference between the two callers is where the subject comes
* from — a prerendered data attribute on a mint page, a resolved API response in the
* dialog — so it is a function rather than a value, read at submit time.
*
* The form is three panels deep, and this module decides which one is on screen: the
* login gate when there is nothing to sign with, the stars alone until one is chosen,
* and the body, identity row and publish button once one is. See WriteForm.astro for
* the markup and the reasoning behind that order.
*/
import { useI18n } from '../i18n/client';
import { escapeHtml } from './client';
@@ -64,9 +69,20 @@ export interface WriteFormOptions {
onBackdrop?: () => boolean;
}
/** What `open` can be told about the reader who is opening it. */
export interface OpenOptions {
/**
* A rating the reader has already chosen elsewhere, checked on their behalf.
*
* The reviews panel's inline "Rate this mint" row is five stars, and pressing one of
* them is the first step of the review, not a request to start it over.
*/
rating?: number;
}
export interface WriteFormApi {
/** Restore any draft, draw the identity row, and focus the rating. */
open(): void;
/** Restore any draft, put up the gate or the stars, and focus the way in. */
open(options?: OpenOptions): void;
close(): void;
/** Clear the form and its stored draft. After publishing, or a subject change. */
reset(): void;
@@ -97,6 +113,12 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
const starLabel = dialog.querySelector<HTMLElement>('[data-star-label]');
const identityRow = dialog.querySelector<HTMLElement>('[data-write-identity]');
const whyDisabled = dialog.querySelector<HTMLElement>('[data-write-why]');
// The three panels the dialog moves between: the login gate, the stars, and
// everything the stars reveal. See WriteForm.astro.
const lede = dialog.querySelector<HTMLElement>('[data-write-lede]');
const gate = dialog.querySelector<HTMLElement>('[data-write-gate]');
const steps = dialog.querySelector<HTMLElement>('[data-write-steps]');
const rest = dialog.querySelector<HTMLElement>('[data-write-rest]');
// One phrase per star, from the catalog, read through `t` rather than held in a table
// here so the phrases follow the page's language after a view transition.
@@ -104,8 +126,21 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
/** True once a publish is in flight, so nothing else runs over it. */
let publishing = false;
/** Who is going to sign, resolved when the dialog opens. */
/** Who is going to sign, resolved when the form itself is revealed. */
let canSign = false;
/** True while the login gate is up, and the form below it is not on screen. */
let gated = false;
/** True once a rating has revealed the rest of the form, for this opening. */
let revealed = false;
/**
* True when the reader pressed a login button *in here*.
*
* A login started from the review dialog is a login started in order to write a
* review, so when it succeeds the dialog comes back by itself. A login started from
* the header is not, and must not pop a review dialog open over whatever the reader
* was actually doing.
*/
let reopenAfterLogin = false;
function showError(message: string): void {
if (!errorBox) return;
@@ -178,6 +213,70 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
syncCount();
}
/* ---------- the three panels ---------- */
/**
* Nothing to sign with: no session, and no NIP-07 extension either.
*
* Asked as the dialog opens rather than on page load, and never cached: an extension
* can be installed, and a session can be started, between one opening and the next.
*/
function needsLogin(): boolean {
return readSession() === null && getExtension() === null;
}
/** Put the login gate up, or take it down and give the form back. */
function setGate(on: boolean): void {
gated = on;
if (gate) gate.hidden = !on;
if (steps) steps.hidden = on;
// The lede invites a review; the gate has its own heading and its own sentence.
if (lede) lede.hidden = on;
}
/**
* Show the body, the identity row and the publish button, once there is a rating.
*
* The rating is the one thing every review carries and the only thing a reader has
* to decide before anything else makes sense, so it is the whole of the first step.
* The identity row is drawn here rather than on open for the same reason it was
* never drawn on page load: asking an extension for a pubkey is a question, and it
* is worth asking only of someone who has started writing.
*/
function syncReveal(): void {
const on = !gated && chosenRating() > 0;
if (rest) rest.hidden = !on;
if (!on || revealed) return;
revealed = true;
void renderIdentity();
}
/**
* Put the cursor on whatever the reader has to do next.
*
* The body when the stars are behind them, the star they already chose when they are
* coming back to it, the first star otherwise. Never the close button, which is what
* a dialog focuses when nothing else asks for it.
*/
function focusEntry(): void {
if (rest?.hidden === false && textarea) {
textarea.focus();
return;
}
const checked = form?.querySelector<HTMLInputElement>('input[name="rating"]:checked');
(checked ?? form?.querySelector<HTMLInputElement>('input[name="rating"]'))?.focus();
}
/** Check a star from outside the form: the panel's inline "Rate this mint" row. */
function setRating(rating: number): void {
const star = form?.querySelector<HTMLInputElement>(`input[name="rating"][value="${rating}"]`);
if (!star) return;
star.checked = true;
syncStarLabel();
syncSubmit();
saveDraft();
}
/* ---------- the form's own state ---------- */
function chosenRating(): number {
@@ -212,13 +311,12 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
function syncSubmit(): void {
if (!submit) return;
const length = textarea?.value.trim().length ?? 0;
const rating = chosenRating();
const over = (textarea?.value.length ?? 0) - MAX_CHARS;
// No `!rating` branch: the actions row does not exist until a star is checked, so
// "Choose a rating" is said once, beside the stars, and never twice.
const reason = publishing
? ''
: !canSign ? t('reviews.dialog.why.login')
: !rating ? t('reviews.dialog.why.rating')
: length === 0 ? t('reviews.dialog.why.words')
: length < MIN_CHARS ? t('reviews.dialog.why.more', { n: MIN_CHARS - length })
: over > 0 ? t('reviews.dialog.why.over', { n: over })
@@ -364,9 +462,50 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
if (dialog.open && publishing) showStatus(update.message);
});
// Logging in from the identity row redraws it, without closing this dialog.
onSessionChange(() => {
if (dialog.open) void renderIdentity();
/*
* A login landed. What that means depends on what this dialog was doing.
*
* Gated, and still open behind the popup: the gate comes down and the reader is
* looking at the stars for the mint they pressed "write a review" on, which is what
* they asked for two clicks ago.
*
* Closed, but the login started in here: it comes back, for the same subject, rather
* than leaving them on a page with nothing to show for the login they just finished.
*
* Open mid-review, logged in from the identity row: only the row itself changes.
*/
onSessionChange((session) => {
if (session && gated && dialog.open) {
reopenAfterLogin = false;
setGate(false);
syncReveal();
// After the popup, not under it: focus cannot leave an open modal, and the login
// popup closes itself a moment after the session lands, handing focus back to the
// gate button that is no longer on screen.
window.setTimeout(focusEntry, 0);
return;
}
/*
* The dialog is gone but the login it asked for has landed.
*
* `isConnected` because this subscription outlives the dialog it closes over: the
* router replaces the page's markup on every navigation and runs the island's setup
* again, so a session arriving after two navigations reaches three of these, two of
* them holding a dialog that is no longer in the document. showModal() throws on
* one of those.
*/
if (session && reopenAfterLogin && !dialog.open) {
reopenAfterLogin = false;
if (dialog.isConnected) openForm();
return;
}
if (dialog.open && revealed) void renderIdentity();
});
// Any login button inside this dialog — the gate's, or the identity row's — is one
// the reader pressed on their way to publishing something.
dialog.addEventListener('click', (event) => {
if ((event.target as HTMLElement).closest('[data-login-open]')) reopenAfterLogin = true;
});
textarea?.addEventListener('input', () => {
@@ -374,11 +513,31 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
saveDraft();
});
/**
* True when the rating about to change is changing because a star was clicked.
*
* Both of a label's clicks — the one on the star and the one it forwards to the
* radio — are dispatched before the radio's checkedness changes, so this is the only
* place the two can be told apart. `detail` is 0 for a click the keyboard produced
* (Space on a focused star); arrow keys walk a radio group with no click at all.
*/
let pointerRating = false;
form?.addEventListener('click', (event) => {
pointerRating = event.detail > 0 && (event.target as HTMLElement).closest('.star-option') !== null;
});
form?.addEventListener('change', (event) => {
if ((event.target as HTMLInputElement).name !== 'rating') return;
syncStarLabel();
syncReveal();
syncSubmit();
saveDraft();
// Pressed a star: the rating is decided and the body is where they are going next.
// Arrowed onto one: they are still choosing, and moving focus out of the group
// would take the arrow keys away mid-decision.
if (pointerRating && rest?.hidden === false) textarea?.focus();
pointerRating = false;
});
/* ---------- publishing ---------- */
@@ -461,6 +620,10 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
form.reset();
syncStarLabel();
// Back to the stars, so the dialog is not showing a full empty form as it closes
// and is not showing one when it next opens.
revealed = false;
syncReveal();
syncCount();
setBusy(false);
showStatus('');
@@ -476,25 +639,43 @@ export function wireWriteForm(options: WriteFormOptions): WriteFormApi {
}
});
return {
open(): void {
/**
* Open the dialog, on whichever of the three panels the reader belongs on.
*
* A rating passed in is one they have already given — the panel's inline star row
* hands one over — so it is checked before the dialog is on screen and the form
* opens with the body already revealed and focused. Nobody should have to press the
* same star twice.
*/
function openForm(options: OpenOptions = {}): void {
showError('');
showStatus('');
setBusy(false);
revealed = false;
restoreDraft();
if (options.rating) setRating(options.rating);
setGate(needsLogin());
syncReveal();
openModal(dialog);
void renderIdentity();
// Somewhere to land that is not the close button: the rating is the first thing
// to decide, and a checked star keeps its own place in the group.
const checked = form?.querySelector<HTMLInputElement>('input[name="rating"]:checked');
(checked ?? form?.querySelector<HTMLInputElement>('input[name="rating"]'))?.focus();
},
if (gated) {
gate?.querySelector<HTMLButtonElement>('[data-login-open]')?.focus();
return;
}
focusEntry();
}
return {
open: openForm,
close(): void {
closeModal(dialog);
},
reset(): void {
form?.reset();
revealed = false;
syncStarLabel();
syncReveal();
syncCount();
showError('');
showStatus('');
+39
View File
@@ -1604,6 +1604,35 @@ html.modal-open, html.modal-open body { overflow: hidden; }
*/
.write-dialog { max-width: 460px; }
/*
* The login gate, and the two panels it stands in front of.
*
* `hidden` alone will not hide any of the three: they are flex columns, and an author
* `display` beats the user agent's `[hidden] { display: none }`. Each one has to take
* it back, the same way the review-by-URL steps do.
*/
.write-gate, .write-steps, .write-rest {
display: flex; flex-direction: column; gap: 14px;
}
.write-gate[hidden], .write-steps[hidden], .write-rest[hidden] { display: none; }
.write-gate-title { font-family: var(--display); font-size: 16px; font-weight: 600; }
.write-gate-lead { font-size: 13.5px; color: var(--muted); line-height: 1.55; }
/*
* The body, the identity row and the publish button arrive together, once a rating is
* chosen. A short rise rather than an appearance, so it reads as the form continuing
* rather than as the dialog changing into a different one.
*/
.write-rest:not([hidden]) { animation: reveal-rise 200ms var(--ease-out) both; }
@keyframes reveal-rise {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
.write-rest:not([hidden]) { animation: fade-in .15s both; }
}
/* Who is about to sign, said before the reader commits to writing anything. */
.write-id {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
@@ -1701,6 +1730,14 @@ html.modal-open, html.modal-open body { overflow: hidden; }
.write-actions { display: flex; gap: 10px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }
/* Why the publish button is off, in the place someone looks when it is off. */
.write-why { font-size: 12.5px; color: var(--faint); margin-right: auto; }
/*
* What publishing does, under the button that does it. It used to be in the dialog's
* heading, where it was a warning about a form nobody had filled in yet; here it is
* the last thing read before the press.
*/
.write-permanent {
font-size: 12px; color: var(--faint); text-align: right; margin-top: -4px;
}
/* The button's own spinner, shown only while a publish is in flight. */
.write-spinner { display: none; }
@@ -1719,6 +1756,8 @@ html.modal-open, html.modal-open body { overflow: hidden; }
@media (max-width: 480px) {
.write-actions .btn { flex: 1; justify-content: center; }
.write-why { width: 100%; margin-right: 0; }
/* The buttons are full width down here, so the line under them is centred on both. */
.write-permanent { text-align: center; }
}
/* A disabled primary keeps its amber, dimmed. Falling back to the card colour on
+1 -1
View File
@@ -2,7 +2,7 @@
* The review body renderer, against hostile relay content.
*
* `lib/review-body.ts` is deliberately import-free so this file can load it under
* plain node with type stripping: `pnpm test` (node --experimental-strip-types).
* plain node with type stripping: `pnpm test` (node --test).
* If these fail, an event someone can sign today can break or script the card.
*/
import test from 'node:test';