From 45e8b970073b0cbbebfb65609b17e540c4e24962 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 12:19:43 +0530 Subject: [PATCH 01/28] feat: add Keytrace profile composable and API integration Co-authored-by: Copilot --- app/components/AccountItem.vue | 368 +++++++++++++++++++++++ app/components/Compare/FacetSelector.vue | 32 +- app/components/LinkedAccounts.vue | 51 ++++ app/components/ProfileHeader.vue | 71 +++++ app/composables/useKeytraceProfile.ts | 54 ++++ app/pages/profile/[identity]/index.vue | 41 ++- i18n/locales/ar-EG.json | 5 +- i18n/locales/ar.json | 5 +- i18n/locales/az-AZ.json | 5 +- i18n/locales/bg-BG.json | 5 +- i18n/locales/bn-IN.json | 5 +- i18n/locales/cs-CZ.json | 5 +- i18n/locales/de-AT.json | 5 + i18n/locales/de-DE.json | 7 +- i18n/locales/de.json | 5 +- i18n/locales/en-GB.json | 5 + i18n/locales/en.json | 3 + i18n/locales/es-419.json | 5 +- i18n/locales/es.json | 5 +- i18n/locales/fr-FR.json | 5 +- i18n/locales/hi-IN.json | 5 +- i18n/locales/hu-HU.json | 5 +- i18n/locales/id-ID.json | 5 +- i18n/locales/it-IT.json | 5 +- i18n/locales/ja-JP.json | 5 +- i18n/locales/kn-IN.json | 5 +- i18n/locales/mr-IN.json | 5 +- i18n/locales/nb-NO.json | 5 +- i18n/locales/ne-NP.json | 5 +- i18n/locales/nl.json | 5 +- i18n/locales/pl-PL.json | 5 +- i18n/locales/pt-BR.json | 5 +- i18n/locales/ru-RU.json | 5 +- i18n/locales/sr-Latn-RS.json | 5 +- i18n/locales/ta-IN.json | 5 +- i18n/locales/te-IN.json | 5 +- i18n/locales/tr-TR.json | 5 +- i18n/locales/uk-UA.json | 5 +- i18n/locales/vi-VN.json | 5 +- i18n/locales/zh-CN.json | 5 +- i18n/locales/zh-TW.json | 5 +- i18n/schema.json | 9 + server/api/keytrace/[domain].ts | 125 ++++++++ server/api/keytrace/reverify.post.ts | 30 ++ shared/types/keytrace.ts | 50 +++ 45 files changed, 948 insertions(+), 58 deletions(-) create mode 100644 app/components/AccountItem.vue create mode 100644 app/components/LinkedAccounts.vue create mode 100644 app/components/ProfileHeader.vue create mode 100644 app/composables/useKeytraceProfile.ts create mode 100644 server/api/keytrace/[domain].ts create mode 100644 server/api/keytrace/reverify.post.ts create mode 100644 shared/types/keytrace.ts diff --git a/app/components/AccountItem.vue b/app/components/AccountItem.vue new file mode 100644 index 0000000000..25ac23b149 --- /dev/null +++ b/app/components/AccountItem.vue @@ -0,0 +1,368 @@ + + + diff --git a/app/components/Compare/FacetSelector.vue b/app/components/Compare/FacetSelector.vue index 7ce289779c..a66d6116e0 100644 --- a/app/components/Compare/FacetSelector.vue +++ b/app/components/Compare/FacetSelector.vue @@ -101,30 +101,30 @@ function deselectAllFacet(category: string) { :aria-labelledby="`facet-category-label-${category}`" data-facet-category-facets > - + + + diff --git a/app/components/LinkedAccounts.vue b/app/components/LinkedAccounts.vue new file mode 100644 index 0000000000..994be8d3d7 --- /dev/null +++ b/app/components/LinkedAccounts.vue @@ -0,0 +1,51 @@ + + + diff --git a/app/components/ProfileHeader.vue b/app/components/ProfileHeader.vue new file mode 100644 index 0000000000..921de318ed --- /dev/null +++ b/app/components/ProfileHeader.vue @@ -0,0 +1,71 @@ + + + diff --git a/app/composables/useKeytraceProfile.ts b/app/composables/useKeytraceProfile.ts new file mode 100644 index 0000000000..58787a1dab --- /dev/null +++ b/app/composables/useKeytraceProfile.ts @@ -0,0 +1,54 @@ +import type { KeytraceAccount, KeytraceResponse } from "#shared/types/keytrace"; + +const statusPriority: Record = { + verified: 0, + unverified: 1, + stale: 2, + failed: 3, +}; + +export function useKeytraceProfile(domain: MaybeRefOrGetter) { + const asyncData = useFetch( + () => `/api/keytrace/${encodeURIComponent(toValue(domain))}`, + { + default: () => ({ + profile: { + name: "", + avatar: "", + description: "", + }, + accounts: [], + }), + }, + ); + + const profile = computed(() => asyncData.data.value?.profile); + const accounts = computed(() => asyncData.data.value?.accounts ?? []); + + const sortedAccounts = computed(() => + [...accounts.value].sort((a, b) => { + const statusSort = statusPriority[a.status] - statusPriority[b.status]; + if (statusSort !== 0) { + return statusSort; + } + + return a.platform.localeCompare(b.platform); + }), + ); + + const verifiedAccounts = computed(() => + sortedAccounts.value.filter((account) => account.status === "verified"), + ); + + const nonVerifiedAccounts = computed(() => + sortedAccounts.value.filter((account) => account.status !== "verified"), + ); + + return { + profile, + accounts: sortedAccounts, + verifiedAccounts, + nonVerifiedAccounts, + loading: asyncData.pending, + }; +} diff --git a/app/pages/profile/[identity]/index.vue b/app/pages/profile/[identity]/index.vue index 61ca0f69eb..284a70e76a 100644 --- a/app/pages/profile/[identity]/index.vue +++ b/app/pages/profile/[identity]/index.vue @@ -2,6 +2,7 @@ import { updateProfile as updateProfileUtil } from '~/utils/atproto/profile' import type { CommandPaletteContextCommandInput } from '~/types/command-palette' import { getSafeHttpUrl } from '#shared/utils/url' +import { useKeytraceProfile } from '~/composables/useKeytraceProfile' const route = useRoute('profile-identity') const identity = computed(() => route.params.identity) @@ -97,6 +98,12 @@ const inviteUrl = computed(() => { }) const safeProfileWebsiteUrl = computed(() => getSafeHttpUrl(profile.value.website)) +const { + profile: keytraceProfile, + accounts: keytraceAccounts, + loading: keytraceLoading, +} = useKeytraceProfile(identity) + useCommandPaletteContextCommands( computed((): CommandPaletteContextCommandInput[] => { const commands: CommandPaletteContextCommandInput[] = [] @@ -232,20 +239,34 @@ defineOgImage( +
+ + +
+
-

- {{ $t('profile.likes') }} - ({{ likes.records?.length ?? 0 }}) -

+
+

+ {{ $t('profile.likes') }} + ({{ likes.records?.length ?? 0 }}) +

+

{{ $t('profile.public_interests_description') }}

+
-
-

{{ $t('common.error') }}

+
+

{{ $t('profile.likes_error') }}

+
+
+

{{ $t('profile.likes_empty') }}

diff --git a/i18n/locales/ar-EG.json b/i18n/locales/ar-EG.json index 18682843b5..83ce5e2dcb 100644 --- a/i18n/locales/ar-EG.json +++ b/i18n/locales/ar-EG.json @@ -113,7 +113,10 @@ "message": "انضم إليّ على npmx", "share_button": "دعوة صديق", "compose_text": "جرّب npmx، المتصفح السريع لحزم npm!" - } + }, + "public_interests_description": "اهتمامات الحزم العامة وإشارات النشاط.", + "likes_error": "تعذّر تحميل الحزم المُعجَب بها.", + "likes_empty": "لا توجد حزم مُعجَب بها بعد." }, "package": { "size_increase": { diff --git a/i18n/locales/ar.json b/i18n/locales/ar.json index 179ed6251f..ad72cb012e 100644 --- a/i18n/locales/ar.json +++ b/i18n/locales/ar.json @@ -155,7 +155,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "اهتمامات الحزم العامة وإشارات النشاط.", + "likes_error": "تعذر تحميل الحزم المفضلة.", + "likes_empty": "لا توجد حزم مفضلة بعد." }, "package": { "not_found": "لم يتم العثور على الحزمة", diff --git a/i18n/locales/az-AZ.json b/i18n/locales/az-AZ.json index 93c9a8117a..d21379292f 100644 --- a/i18n/locales/az-AZ.json +++ b/i18n/locales/az-AZ.json @@ -214,7 +214,10 @@ "message": "Deyəsən onlar hələ npmx istifadə etmirlər. Onlara bu barədə demək istəyirsiniz?", "share_button": "Bluesky-da paylaş", "compose_text": "Salam {'@'}{handle}! npmx.dev-i yoxlamısan? Bu, npm reyestri üçün sürətli, müasir və açıq mənbəli brauzerdir.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Açıq paket maraqları və fəaliyyət siqnalları.", + "likes_error": "Bəyənilən paketlər yüklənə bilmədi.", + "likes_empty": "Hələ bəyənilən paket yoxdur." }, "package": { "not_found": "Paket Tapılmadı", diff --git a/i18n/locales/bg-BG.json b/i18n/locales/bg-BG.json index f937b02e1e..daa106948f 100644 --- a/i18n/locales/bg-BG.json +++ b/i18n/locales/bg-BG.json @@ -177,7 +177,10 @@ "message": "Изглежда, че все още не използват npmx. Искате ли да им кажете за него?", "share_button": "Споделете в Bluesky", "compose_text": "Здравей {'@'}{handle}! Проверил ли си npmx.dev? Това е браузър за npm регистъра - бърз, модерен и с отворен код.\\nhttps://npmx.dev" - } + }, + "public_interests_description": "Публични интереси към пакети и сигнали за активност.", + "likes_error": "Неуспешно зареждане на харесани пакети.", + "likes_empty": "Все още няма харесани пакети." }, "package": { "not_found": "Пакетът не е намерен", diff --git a/i18n/locales/bn-IN.json b/i18n/locales/bn-IN.json index 3712252621..883027ec71 100644 --- a/i18n/locales/bn-IN.json +++ b/i18n/locales/bn-IN.json @@ -121,7 +121,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "পাবলিক প্যাকেজ আগ্রহ এবং কার্যকলাপ সংকেত।", + "likes_error": "পছন্দের প্যাকেজগুলি লোড করা যায়নি।", + "likes_empty": "এখনও কোনো পছন্দের প্যাকেজ নেই।" }, "package": { "not_found": "প্যাকেজ পাওয়া যায়নি", diff --git a/i18n/locales/cs-CZ.json b/i18n/locales/cs-CZ.json index b5ffca3a02..84cbc676be 100644 --- a/i18n/locales/cs-CZ.json +++ b/i18n/locales/cs-CZ.json @@ -359,7 +359,10 @@ "message": "Zdá se, že ještě nepoužívají npmx. Chcete jim o tom říct?", "share_button": "Sdílet na Bluesky", "compose_text": "Ahoj {'@'}{handle}! Viděl jsi už npmx.dev? Je to prohlížeč pro npm registr, který je rychlý, moderní a open-source.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Veřejné zájmy o balíčky a signály aktivity.", + "likes_error": "Nepodařilo se načíst oblíbené balíčky.", + "likes_empty": "Zatím žádné oblíbené balíčky." }, "package": { "not_found": "Balíček nenalezen", diff --git a/i18n/locales/de-AT.json b/i18n/locales/de-AT.json index 458cf38f23..daeddeec95 100644 --- a/i18n/locales/de-AT.json +++ b/i18n/locales/de-AT.json @@ -21,5 +21,10 @@ "instant_search_turn_on": "Schnellsuche aktivieren", "instant_search_turn_off": "Schnellsuche deaktivieren", "instant_search_advisory": "Die Schnellsuche sendet bei jedem Tastendruck eine Anfrage." + }, + "profile": { + "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", + "likes_error": "Beliebte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine beliebten Pakete vorhanden." } } diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json index 0618235d76..f439792e96 100644 --- a/i18n/locales/de-DE.json +++ b/i18n/locales/de-DE.json @@ -1,3 +1,8 @@ { - "$schema": "../schema.json" + "$schema": "../schema.json", + "profile": { + "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", + "likes_error": "Gelikte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine gelikten Pakete." + } } diff --git a/i18n/locales/de.json b/i18n/locales/de.json index e805f32888..1a29179809 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -358,7 +358,10 @@ "message": "Es sieht nicht so aus, als ob sie npmx schon benutzen. Möchtest du ihnen davon erzählen?", "share_button": "Auf Bluesky teilen", "compose_text": "Hey {'@'}{handle}! Hast du schon npmx.dev ausprobiert? Es ist ein Browser für die npm Registry, der schnell, modern und Open-Source ist.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", + "likes_error": "Gelikte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine gelikten Pakete." }, "package": { "not_found": "Paket nicht gefunden", diff --git a/i18n/locales/en-GB.json b/i18n/locales/en-GB.json index cd250a7f15..127ed8f8ef 100644 --- a/i18n/locales/en-GB.json +++ b/i18n/locales/en-GB.json @@ -45,5 +45,10 @@ "error": "Failed to load organisations", "empty": "No organisations found" } + }, + "profile": { + "public_interests_description": "Public package interests and activity signals.", + "likes_error": "Could not load liked packages.", + "likes_empty": "No liked packages yet." } } diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 9c132582da..5e31a60a21 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -355,6 +355,9 @@ "website": "Website", "website_placeholder": "https://example.com", "likes": "Likes", + "public_interests_description": "Public package interests and activity signals.", + "likes_error": "Could not load liked packages.", + "likes_empty": "No liked packages yet.", "seo_title": "{handle} - npmx", "seo_description": "npmx profile by {handle}", "not_found": "Profile Not Found", diff --git a/i18n/locales/es-419.json b/i18n/locales/es-419.json index aff49a7df8..ad0eae137c 100644 --- a/i18n/locales/es-419.json +++ b/i18n/locales/es-419.json @@ -43,7 +43,10 @@ "invite": { "message": "Parece que aún no usa npmx. ¿Quieres contarle?", "compose_text": "¡Hola {'@'}{handle}! ¿Has probado ya npmx.dev? Es un navegador para el registro de npm rápido, moderno y de código abierto.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Intereses de paquetes públicos y señales de actividad.", + "likes_error": "No se pudieron cargar los paquetes favoritos.", + "likes_empty": "Aún no hay paquetes favoritos." }, "package": { "readme": { diff --git a/i18n/locales/es.json b/i18n/locales/es.json index 9bcb7263fc..a723b3e57c 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -244,7 +244,10 @@ "message": "Parece que aún no usa npmx. ¿Quieres contárselo?", "share_button": "Compartir en Bluesky", "compose_text": "¡Hola {'@'}{handle}! ¿Has probado ya npmx.dev? Es un navegador para el registro de npm rápido, moderno y de código abierto.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Intereses públicos en paquetes y señales de actividad.", + "likes_error": "No se pudieron cargar los paquetes que te gustan.", + "likes_empty": "Aún no hay paquetes que te gusten." }, "package": { "not_found": "Paquete no encontrado", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 0e40fa415b..01c18381a0 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -360,7 +360,10 @@ "message": "Il semblerait qu'ils n'utilisent pas encore npmx. Vous voulez leur en parler ?", "share_button": "Partager sur Bluesky", "compose_text": "Salut {'@'}{handle} ! As-tu déjà testé npmx.dev ? C'est un navigateur pour le registre npm : rapide, moderne et open source.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Intérêts publics pour les paquets et signaux d'activité.", + "likes_error": "Impossible de charger les paquets aimés.", + "likes_empty": "Aucun paquet aimé pour le moment." }, "package": { "not_found": "Paquet introuvable", diff --git a/i18n/locales/hi-IN.json b/i18n/locales/hi-IN.json index ea702a7728..18391f03c2 100644 --- a/i18n/locales/hi-IN.json +++ b/i18n/locales/hi-IN.json @@ -229,7 +229,10 @@ "expand": "विस्तृत करें" }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "सार्वजनिक पैकेज रुचियाँ और गतिविधि संकेत।", + "likes_error": "पसंद किए गए पैकेज लोड नहीं हो सके।", + "likes_empty": "अभी तक कोई पसंद किए गए पैकेज नहीं।" }, "package": { "not_found": "पैकेज नहीं मिला", diff --git a/i18n/locales/hu-HU.json b/i18n/locales/hu-HU.json index c14948a6e1..0b391fa6f2 100644 --- a/i18n/locales/hu-HU.json +++ b/i18n/locales/hu-HU.json @@ -177,7 +177,10 @@ "message": "Úgy tűnik, hogy még nem használja az npmx-et. Szeretnéd megtudatni vele?", "share_button": "Megosztás a Bluesky-on", "compose_text": "Halló {'@'}{handle}! Már próbáltad az npmx.dev-et? Egy gyors, modern és nyílt forráskódú böngésző az npm regiszterhez.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Nyilvános csomagérdeklődések és aktivitási jelek.", + "likes_error": "Nem sikerült betölteni a kedvelt csomagokat.", + "likes_empty": "Még nincsenek kedvelt csomagok." }, "package": { "not_found": "Csomag Nem Található", diff --git a/i18n/locales/id-ID.json b/i18n/locales/id-ID.json index 9646ca17b1..717b761a39 100644 --- a/i18n/locales/id-ID.json +++ b/i18n/locales/id-ID.json @@ -244,7 +244,10 @@ "message": "Sepertinya mereka belum menggunakan npmx. Ingin memberi tahu mereka?", "share_button": "Bagikan di Bluesky", "compose_text": "Hai {'@'}{handle}! Sudah pernah mencoba npmx.dev? Ini adalah browser untuk npm registry yang cepat, modern, dan open-source.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Minat paket publik dan sinyal aktivitas.", + "likes_error": "Gagal memuat paket yang disukai.", + "likes_empty": "Belum ada paket yang disukai." }, "package": { "not_found": "Paket Tidak Ditemukan", diff --git a/i18n/locales/it-IT.json b/i18n/locales/it-IT.json index b19efca8db..dfbb4c95d1 100644 --- a/i18n/locales/it-IT.json +++ b/i18n/locales/it-IT.json @@ -155,7 +155,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "Interessi dei pacchetti pubblici e segnali di attività.", + "likes_error": "Impossibile caricare i pacchetti preferiti.", + "likes_empty": "Nessun pacchetto preferito ancora." }, "package": { "not_found": "Pacchetto Non Trovato", diff --git a/i18n/locales/ja-JP.json b/i18n/locales/ja-JP.json index 72397e4907..63e51d154a 100644 --- a/i18n/locales/ja-JP.json +++ b/i18n/locales/ja-JP.json @@ -224,7 +224,10 @@ "message": "まだnpmxを利用していないようです。npmxを紹介しますか?", "share_button": "Blueskyで共有", "compose_text": "{'@'}{handle} さん、npmx.devはもうチェックしましたか? 高速でモダンなオープンソースのnpmレジストリブラウザです。\nhttps://npmx.dev" - } + }, + "public_interests_description": "公開パッケージの関心事とアクティビティシグナル。", + "likes_error": "いいねしたパッケージを読み込めませんでした。", + "likes_empty": "いいねしたパッケージはまだありません。" }, "package": { "not_found": "パッケージが見つかりません", diff --git a/i18n/locales/kn-IN.json b/i18n/locales/kn-IN.json index 2111c4dd23..d8d81eb9e7 100644 --- a/i18n/locales/kn-IN.json +++ b/i18n/locales/kn-IN.json @@ -122,7 +122,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "ಸಾರ್ವಜನಿಕ ಪ್ಯಾಕೇಜ್ ಆಸಕ್ತಿಗಳು ಮತ್ತು ಚಟುವಟಿಕೆ ಸಂಕೇತಗಳು.", + "likes_error": "ಇಷ್ಟಪಟ್ಟ ಪ್ಯಾಕೇಜುಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "likes_empty": "ಇನ್ನೂ ಇಷ್ಟಪಟ್ಟ ಪ್ಯಾಕೇಜುಗಳಿಲ್ಲ." }, "package": { "not_found": "ಪ್ಯಾಕೇಜ್ ಕಂಡುಬಂದಿಲ್ಲ", diff --git a/i18n/locales/mr-IN.json b/i18n/locales/mr-IN.json index 57c36430e0..7bee21b07b 100644 --- a/i18n/locales/mr-IN.json +++ b/i18n/locales/mr-IN.json @@ -213,7 +213,10 @@ "expand": "विस्तृत करा" }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "सार्वजनिक पॅकेज आवडी आणि क्रियाकलाप संकेत.", + "likes_error": "आवडलेल्या पॅकेजेस लोड करता आल्या नाहीत.", + "likes_empty": "अद्याप आवडलेली कोणतीही पॅकेजेस नाहीत." }, "package": { "not_found": "पॅकेज सापडले नाही", diff --git a/i18n/locales/nb-NO.json b/i18n/locales/nb-NO.json index 1d6351607a..749c355749 100644 --- a/i18n/locales/nb-NO.json +++ b/i18n/locales/nb-NO.json @@ -358,7 +358,10 @@ "message": "Det ser ikke ut som de bruker npmx ennå. Vil du fortelle dem om det?", "share_button": "Del på Bluesky", "compose_text": "Hei {'@'}{handle}! Har du sjekket ut npmx.dev ennå? Det er en leser for npm-registeret som er rask, moderne og åpen kildekode.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Offentlige pakkeinteresser og aktivitetssignaler.", + "likes_error": "Kunne ikke laste likte pakker.", + "likes_empty": "Ingen likte pakker ennå." }, "package": { "not_found": "Pakke ikke funnet", diff --git a/i18n/locales/ne-NP.json b/i18n/locales/ne-NP.json index 60b7dec1f1..c348e2fe17 100644 --- a/i18n/locales/ne-NP.json +++ b/i18n/locales/ne-NP.json @@ -122,7 +122,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "सार्वजनिक प्याकेज रुचिहरू र गतिविधि संकेतहरू।", + "likes_error": "मनपरेका प्याकेजहरू लोड गर्न सकिएन।", + "likes_empty": "अहिले सम्म मनपरेका प्याकेजहरू छैनन्।" }, "package": { "not_found": "प्याकेज फेला परेन", diff --git a/i18n/locales/nl.json b/i18n/locales/nl.json index 85e5f13c1a..45634a2503 100644 --- a/i18n/locales/nl.json +++ b/i18n/locales/nl.json @@ -358,7 +358,10 @@ "message": "Het lijkt erop dat ze npmx nog niet gebruiken. Wilt u ze hierop wijzen", "share_button": "Deel op Bluesky", "compose_text": "Hallo {'@'}{handle}! Hebt u npmx.dev al bekeken? Het is een browser voor het npm register die snel, modern en open source is.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Openbare pakketinteresses en activiteitssignalen.", + "likes_error": "Kon gelikete pakketten niet laden.", + "likes_empty": "Nog geen gelikete pakketten." }, "package": { "not_found": "Pakket niet gevonden", diff --git a/i18n/locales/pl-PL.json b/i18n/locales/pl-PL.json index f4acd249ed..85cfd34c32 100644 --- a/i18n/locales/pl-PL.json +++ b/i18n/locales/pl-PL.json @@ -224,7 +224,10 @@ "message": "Wygląda na to, że jeszcze nie korzystają z npmx. Chcesz ich powiadomić?", "share_button": "Podziel się na Bluesky", "compose_text": "Hej {'@'}{handle}! Czy znasz już npmx.dev? To szybka, nowoczesna przeglądarka rejestru npm z otwartym kodem źródłowym.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Publiczne zainteresowania pakietami i sygnały aktywności.", + "likes_error": "Nie udało się wczytać polubionych pakietów.", + "likes_empty": "Brak polubionych pakietów." }, "package": { "not_found": "Nie znaleziono pakietu", diff --git a/i18n/locales/pt-BR.json b/i18n/locales/pt-BR.json index 935b61c5ec..a32f720e61 100644 --- a/i18n/locales/pt-BR.json +++ b/i18n/locales/pt-BR.json @@ -355,7 +355,10 @@ "message": "Parece que eles ainda não estão usando o npmx. Quer contar a eles sobre isso?", "share_button": "Compartilhar no Bluesky", "compose_text": "Olá, {'@'}{handle}! Você já conferiu npmx.dev? É um navegador para o registro npm rápido, moderno e de código aberto.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Interesses públicos de pacotes e sinais de atividade.", + "likes_error": "Não foi possível carregar os pacotes curtidos.", + "likes_empty": "Nenhum pacote curtido ainda." }, "package": { "not_found": "Pacote não encontrado", diff --git a/i18n/locales/ru-RU.json b/i18n/locales/ru-RU.json index 66dc858a5a..bc9adaa933 100644 --- a/i18n/locales/ru-RU.json +++ b/i18n/locales/ru-RU.json @@ -360,7 +360,10 @@ "message": "Похоже, этот пользователь ещё не пользуется npmx. Хотите рассказать ему о проекте?", "share_button": "Поделиться в Bluesky", "compose_text": "Привет, {'@'}{handle}! Уже смотрел npmx.dev? Это быстрый, современный и open-source браузер для реестра npm.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Публичные интересы к пакетам и сигналы активности.", + "likes_error": "Не удалось загрузить понравившиеся пакеты.", + "likes_empty": "Понравившихся пакетов пока нет." }, "package": { "not_found": "Пакет не найден", diff --git a/i18n/locales/sr-Latn-RS.json b/i18n/locales/sr-Latn-RS.json index c015089b8c..e7c98216c7 100644 --- a/i18n/locales/sr-Latn-RS.json +++ b/i18n/locales/sr-Latn-RS.json @@ -244,7 +244,10 @@ "message": "Izgleda da još uvek ne koriste npmx. Želite li da im kažete nešto više o tome?", "share_button": "Podelite na Bluesky-u", "compose_text": "Hej {'@'}{handle}! Da li ste već pogledali npmx.dev? To je pretraživač za npm registar koji je brz, moderan i otvorenog koda.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Javna interesovanja za pakete i signali aktivnosti.", + "likes_error": "Nije moguće učitati omiljene pakete.", + "likes_empty": "Još nema omiljenih paketa." }, "package": { "not_found": "Paket nije pronađen", diff --git a/i18n/locales/ta-IN.json b/i18n/locales/ta-IN.json index e8b657110c..fd8b7cdbf1 100644 --- a/i18n/locales/ta-IN.json +++ b/i18n/locales/ta-IN.json @@ -154,7 +154,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "பொதுப் தொகுப்பு விருப்பங்கள் மற்றும் செயல்பாட்டு சிக்னல்கள்.", + "likes_error": "விரும்பிய தொகுப்புகளை ஏற்ற முடியவில்லை.", + "likes_empty": "இன்னும் விரும்பிய தொகுப்புகள் இல்லை." }, "package": { "not_found": "தொகுப்பு கிடைக்கவில்லை", diff --git a/i18n/locales/te-IN.json b/i18n/locales/te-IN.json index 7e92347d2e..3b1fd100ae 100644 --- a/i18n/locales/te-IN.json +++ b/i18n/locales/te-IN.json @@ -122,7 +122,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "Public package interests and activity signals.", + "likes_error": "Could not load liked packages.", + "likes_empty": "No liked packages yet." }, "package": { "not_found": "ప్యాకేజ్ కనుగొనబడలేదు", diff --git a/i18n/locales/tr-TR.json b/i18n/locales/tr-TR.json index 963fed0027..959564eac2 100644 --- a/i18n/locales/tr-TR.json +++ b/i18n/locales/tr-TR.json @@ -214,7 +214,10 @@ "message": "npmx'i deneyin - npm için daha iyi bir paket tarayıcısı", "share_button": "Paylaş", "compose_text": "npmx'i deneyin - npm için daha iyi bir paket tarayıcısı: {url}" - } + }, + "public_interests_description": "Genel paket ilgi alanları ve aktivite sinyalleri.", + "likes_error": "Beğenilen paketler yüklenemedi.", + "likes_empty": "Henüz beğenilen paket yok." }, "package": { "not_found": "Paket Bulunamadı", diff --git a/i18n/locales/uk-UA.json b/i18n/locales/uk-UA.json index 336c409e1b..b229774241 100644 --- a/i18n/locales/uk-UA.json +++ b/i18n/locales/uk-UA.json @@ -244,7 +244,10 @@ "message": "Схоже, вони ще не користуються npmx. Хочете розповісти їм про нього?", "share_button": "Поділитися в Bluesky", "compose_text": "Привіт, {'@'}{handle}! Ти вже перевірив npmx.dev? Це швидкий сучасний браузер для реєстру npm з відкритим кодом.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Публічні інтереси до пакетів та сигнали активності.", + "likes_error": "Не вдалося завантажити вподобані пакети.", + "likes_empty": "Вподобаних пакетів поки немає." }, "package": { "not_found": "Пакет не знайдено", diff --git a/i18n/locales/vi-VN.json b/i18n/locales/vi-VN.json index d9a5698fa2..10721d5f92 100644 --- a/i18n/locales/vi-VN.json +++ b/i18n/locales/vi-VN.json @@ -244,7 +244,10 @@ "message": "Có vẻ họ chưa dùng npmx. Bạn có muốn giới thiệu cho họ không?", "share_button": "Chia sẻ trên Bluesky", "compose_text": "Chào {'@'}{handle}! Bạn đã thử npmx.dev chưa? Đây là trình duyệt cho npm registry, nhanh, hiện đại và mã nguồn mở.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Sở thích package công khai và tín hiệu hoạt động.", + "likes_error": "Không thể tải các package đã thích.", + "likes_empty": "Chưa có package nào được thích." }, "package": { "not_found": "Không tìm thấy gói", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 559283c623..9b99aee730 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -359,7 +359,10 @@ "message": "看起来他们还没有使用 npmx,去邀请一下?", "share_button": "分享到 Bluesky", "compose_text": "嗨 {'@'}{handle}!您用过 npmx.dev 吗?它是一个快速、现代且开源的 npm registry 浏览器。\nhttps://npmx.dev" - } + }, + "public_interests_description": "公开的包兴趣和活动信号。", + "likes_error": "无法加载喜欢的包。", + "likes_empty": "还没有喜欢的包。" }, "package": { "not_found": "未找到包", diff --git a/i18n/locales/zh-TW.json b/i18n/locales/zh-TW.json index 623e3955fe..7843986523 100644 --- a/i18n/locales/zh-TW.json +++ b/i18n/locales/zh-TW.json @@ -357,7 +357,10 @@ "message": "看起來對方還沒在用 npmx。要不要跟他們分享一下?", "share_button": "分享到 Bluesky", "compose_text": "Hey {'@'}{handle}!你用過 npmx.dev 了嗎?它是 npm Registry 的瀏覽器,快速、現代,而且是開源的。\nhttps://npmx.dev" - } + }, + "public_interests_description": "公開套件興趣與活動訊號。", + "likes_error": "無法載入喜歡的套件。", + "likes_empty": "目前尚無喜歡的套件。" }, "package": { "not_found": "找不到套件", diff --git a/i18n/schema.json b/i18n/schema.json index f06f2f53a8..3f7d803d9a 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -1069,6 +1069,15 @@ "likes": { "type": "string" }, + "likes_error": { + "type": "string" + }, + "likes_empty": { + "type": "string" + }, + "public_interests_description": { + "type": "string" + }, "seo_title": { "type": "string" }, diff --git a/server/api/keytrace/[domain].ts b/server/api/keytrace/[domain].ts new file mode 100644 index 0000000000..b2781daa39 --- /dev/null +++ b/server/api/keytrace/[domain].ts @@ -0,0 +1,125 @@ +import type { KeytraceResponse } from "#shared/types/keytrace"; + +const MOCK_KEYTRACE_PROFILES: Record = { + "npmx.dev": { + profile: { + name: "npmx Team", + avatar: "https://api.dicebear.com/9.x/shapes/svg?seed=npmx", + banner: + "https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&fit=crop&w=1400&q=80", + description: + "Open source developers building better tooling around package discovery.", + }, + accounts: [ + { + platform: "github", + username: "npmx-dev", + displayName: "npmx-dev", + avatar: "https://avatars.githubusercontent.com/u/178563400?v=4", + url: "https://github.com/npmx-dev", + status: "verified", + proofMethod: "github", + addedAt: "2026-03-10T12:00:00.000Z", + lastCheckedAt: "2026-04-20T09:30:00.000Z", + }, + { + platform: "npm", + username: "npmx", + displayName: "npmx", + avatar: "https://api.dicebear.com/9.x/identicon/svg?seed=npmx", + url: "https://www.npmjs.com/~npmx", + status: "stale", + proofMethod: "npm", + addedAt: "2026-02-18T15:20:00.000Z", + lastCheckedAt: "2026-03-05T08:00:00.000Z", + failureReason: "Proof has not been re-verified recently.", + }, + { + platform: "mastodon", + username: "@npmx@fosstodon.org", + displayName: "npmx", + avatar: + "https://api.dicebear.com/9.x/identicon/svg?seed=fosstodon-npmx", + url: "https://fosstodon.org/@npmx", + status: "failed", + proofMethod: "mastodon", + addedAt: "2026-01-22T11:40:00.000Z", + lastCheckedAt: "2026-04-19T22:15:00.000Z", + failureReason: + "Linked proof URL could not be resolved during verification.", + }, + ], + }, + "empty.dev": { + profile: { + name: "Empty Developer", + avatar: "https://api.dicebear.com/9.x/initials/svg?seed=empty.dev", + description: "A profile with no linked accounts yet.", + }, + accounts: [], + }, +}; + +function domainToDisplayName(domain: string): string { + const firstSegment = domain.split(".")[0] || domain; + return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1); +} + +function buildFallbackProfile(domain: string): KeytraceResponse { + return { + profile: { + name: `${domainToDisplayName(domain)} Developer`, + avatar: `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(domain)}`, + description: `Mock identity profile for ${domain}.`, + }, + accounts: [ + { + platform: "github", + username: domain, + displayName: domain, + avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=github-${encodeURIComponent(domain)}`, + url: `https://github.com/${domain}`, + status: "verified", + proofMethod: "github", + addedAt: "2026-04-01T10:00:00.000Z", + lastCheckedAt: "2026-04-21T10:00:00.000Z", + }, + { + platform: "npm", + username: domain, + displayName: domain, + avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=npm-${encodeURIComponent(domain)}`, + url: `https://www.npmjs.com/~${domain}`, + status: "unverified", + proofMethod: "npm", + addedAt: "2026-04-01T10:00:00.000Z", + lastCheckedAt: "2026-04-01T10:00:00.000Z", + failureReason: "Proof exists but has not been verified yet.", + }, + { + platform: "mastodon", + username: `@${domain}@mastodon.social`, + displayName: domain, + avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=mastodon-${encodeURIComponent(domain)}`, + url: `https://mastodon.social/@${encodeURIComponent(domain)}`, + status: "stale", + proofMethod: "mastodon", + addedAt: "2026-04-01T10:00:00.000Z", + lastCheckedAt: "2026-04-08T10:00:00.000Z", + failureReason: "Verification check is out of date.", + }, + ], + }; +} + +export default defineEventHandler((event) => { + const domain = getRouterParam(event, "domain")?.trim().toLowerCase(); + if (!domain) { + throw createError({ + statusCode: 400, + message: "Domain is required", + }); + } + + return MOCK_KEYTRACE_PROFILES[domain] ?? buildFallbackProfile(domain); +}); diff --git a/server/api/keytrace/reverify.post.ts b/server/api/keytrace/reverify.post.ts new file mode 100644 index 0000000000..9030cd85b2 --- /dev/null +++ b/server/api/keytrace/reverify.post.ts @@ -0,0 +1,30 @@ +import type { + KeytraceReverifyRequest, + KeytraceReverifyResponse, +} from "#shared/types/keytrace"; + +export default defineEventHandler(async (event) => { + const body = await readBody(event); + + const platform = body?.platform?.trim().toLowerCase(); + const username = body?.username?.trim(); + + if (!platform || !username) { + throw createError({ + statusCode: 400, + message: "platform and username are required", + }); + } + + const lastCheckedAt = new Date().toISOString(); + + // TODO: Implement per-platform proof verification for KeytraceReverifyResponse + // construction here (including mastodon and npm short-username logic), ideally + // via a verifyProofForPlatform(platform, username, proofData) helper. + const response: KeytraceReverifyResponse = { + status: "unverified", + lastCheckedAt, + failureReason: "Proof verification is not implemented yet.", + }; + return response; +}); diff --git a/shared/types/keytrace.ts b/shared/types/keytrace.ts new file mode 100644 index 0000000000..859974ff42 --- /dev/null +++ b/shared/types/keytrace.ts @@ -0,0 +1,50 @@ +export type KeytraceProfile = { + name: string; + avatar: string; + banner?: string; + description: string; +}; + +export type KeytraceVerificationStatus = + | "verified" + | "unverified" + | "stale" + | "failed"; + +export type KeytraceProofMethod = + | "dns" + | "github" + | "npm" + | "mastodon" + | "pgp" + | "other"; + +export type KeytraceAccount = { + platform: string; + username: string; + displayName?: string; + avatar?: string; + url?: string; + status: KeytraceVerificationStatus; + proofMethod: KeytraceProofMethod; + addedAt: string; + lastCheckedAt: string; + failureReason?: string; +}; + +export type KeytraceResponse = { + profile: KeytraceProfile; + accounts: KeytraceAccount[]; +}; + +export type KeytraceReverifyRequest = { + platform: string; + username: string; + url?: string; +}; + +export type KeytraceReverifyResponse = { + status: KeytraceVerificationStatus; + lastCheckedAt: string; + failureReason?: string; +}; From 5043d74f9e028c6395e411f78b67b817bc8cf4ef Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 12:22:05 +0530 Subject: [PATCH 02/28] fix: refactor code by formatting the components and types --- app/components/Compare/FacetSelector.vue | 6 +- app/composables/useKeytraceProfile.ts | 36 +++--- server/api/keytrace/[domain].ts | 135 +++++++++++------------ server/api/keytrace/reverify.post.ts | 29 +++-- shared/types/keytrace.ts | 68 +++++------- 5 files changed, 131 insertions(+), 143 deletions(-) diff --git a/app/components/Compare/FacetSelector.vue b/app/components/Compare/FacetSelector.vue index a66d6116e0..3a30a603c6 100644 --- a/app/components/Compare/FacetSelector.vue +++ b/app/components/Compare/FacetSelector.vue @@ -106,7 +106,11 @@ function deselectAllFacet(category: string) { :key="facet.id" :title="facet.comingSoon ? $t('compare.facets.coming_soon') : facet.description" class="inline-flex items-center gap-1 px-1.5 rounded border transition-colors text-fg-subtle bg-bg-subtle border-border-subtle hover:(text-fg-muted border-border) has-[:checked]:(text-fg-muted bg-fg/10 border-fg/20 hover:(bg-fg/20 text-fg/50)) has-[:focus-visible]:(outline outline-accent/70)" - :class="facet.comingSoon ? 'cursor-not-allowed text-fg-subtle/50 bg-bg-subtle border-border-subtle hover:(text-fg-subtle/50 border-border-subtle)' : 'cursor-pointer'" + :class=" + facet.comingSoon + ? 'cursor-not-allowed text-fg-subtle/50 bg-bg-subtle border-border-subtle hover:(text-fg-subtle/50 border-border-subtle)' + : 'cursor-pointer' + " > = { +const statusPriority: Record = { verified: 0, unverified: 1, stale: 2, failed: 3, -}; +} export function useKeytraceProfile(domain: MaybeRefOrGetter) { const asyncData = useFetch( @@ -13,36 +13,36 @@ export function useKeytraceProfile(domain: MaybeRefOrGetter) { { default: () => ({ profile: { - name: "", - avatar: "", - description: "", + name: '', + avatar: '', + description: '', }, accounts: [], }), }, - ); + ) - const profile = computed(() => asyncData.data.value?.profile); - const accounts = computed(() => asyncData.data.value?.accounts ?? []); + const profile = computed(() => asyncData.data.value?.profile) + const accounts = computed(() => asyncData.data.value?.accounts ?? []) const sortedAccounts = computed(() => [...accounts.value].sort((a, b) => { - const statusSort = statusPriority[a.status] - statusPriority[b.status]; + const statusSort = statusPriority[a.status] - statusPriority[b.status] if (statusSort !== 0) { - return statusSort; + return statusSort } - return a.platform.localeCompare(b.platform); + return a.platform.localeCompare(b.platform) }), - ); + ) const verifiedAccounts = computed(() => - sortedAccounts.value.filter((account) => account.status === "verified"), - ); + sortedAccounts.value.filter(account => account.status === 'verified'), + ) const nonVerifiedAccounts = computed(() => - sortedAccounts.value.filter((account) => account.status !== "verified"), - ); + sortedAccounts.value.filter(account => account.status !== 'verified'), + ) return { profile, @@ -50,5 +50,5 @@ export function useKeytraceProfile(domain: MaybeRefOrGetter) { verifiedAccounts, nonVerifiedAccounts, loading: asyncData.pending, - }; + } } diff --git a/server/api/keytrace/[domain].ts b/server/api/keytrace/[domain].ts index b2781daa39..2b2e20e478 100644 --- a/server/api/keytrace/[domain].ts +++ b/server/api/keytrace/[domain].ts @@ -1,68 +1,65 @@ -import type { KeytraceResponse } from "#shared/types/keytrace"; +import type { KeytraceResponse } from '#shared/types/keytrace' const MOCK_KEYTRACE_PROFILES: Record = { - "npmx.dev": { + 'npmx.dev': { profile: { - name: "npmx Team", - avatar: "https://api.dicebear.com/9.x/shapes/svg?seed=npmx", + name: 'npmx Team', + avatar: 'https://api.dicebear.com/9.x/shapes/svg?seed=npmx', banner: - "https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&fit=crop&w=1400&q=80", - description: - "Open source developers building better tooling around package discovery.", + 'https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&fit=crop&w=1400&q=80', + description: 'Open source developers building better tooling around package discovery.', }, accounts: [ { - platform: "github", - username: "npmx-dev", - displayName: "npmx-dev", - avatar: "https://avatars.githubusercontent.com/u/178563400?v=4", - url: "https://github.com/npmx-dev", - status: "verified", - proofMethod: "github", - addedAt: "2026-03-10T12:00:00.000Z", - lastCheckedAt: "2026-04-20T09:30:00.000Z", + platform: 'github', + username: 'npmx-dev', + displayName: 'npmx-dev', + avatar: 'https://avatars.githubusercontent.com/u/178563400?v=4', + url: 'https://github.com/npmx-dev', + status: 'verified', + proofMethod: 'github', + addedAt: '2026-03-10T12:00:00.000Z', + lastCheckedAt: '2026-04-20T09:30:00.000Z', }, { - platform: "npm", - username: "npmx", - displayName: "npmx", - avatar: "https://api.dicebear.com/9.x/identicon/svg?seed=npmx", - url: "https://www.npmjs.com/~npmx", - status: "stale", - proofMethod: "npm", - addedAt: "2026-02-18T15:20:00.000Z", - lastCheckedAt: "2026-03-05T08:00:00.000Z", - failureReason: "Proof has not been re-verified recently.", + platform: 'npm', + username: 'npmx', + displayName: 'npmx', + avatar: 'https://api.dicebear.com/9.x/identicon/svg?seed=npmx', + url: 'https://www.npmjs.com/~npmx', + status: 'stale', + proofMethod: 'npm', + addedAt: '2026-02-18T15:20:00.000Z', + lastCheckedAt: '2026-03-05T08:00:00.000Z', + failureReason: 'Proof has not been re-verified recently.', }, { - platform: "mastodon", - username: "@npmx@fosstodon.org", - displayName: "npmx", - avatar: - "https://api.dicebear.com/9.x/identicon/svg?seed=fosstodon-npmx", - url: "https://fosstodon.org/@npmx", - status: "failed", - proofMethod: "mastodon", - addedAt: "2026-01-22T11:40:00.000Z", - lastCheckedAt: "2026-04-19T22:15:00.000Z", - failureReason: - "Linked proof URL could not be resolved during verification.", + platform: 'mastodon', + username: '@npmx@fosstodon.org', + displayName: 'npmx', + avatar: 'https://api.dicebear.com/9.x/identicon/svg?seed=fosstodon-npmx', + url: 'https://fosstodon.org/@npmx', + status: 'failed', + proofMethod: 'mastodon', + addedAt: '2026-01-22T11:40:00.000Z', + lastCheckedAt: '2026-04-19T22:15:00.000Z', + failureReason: 'Linked proof URL could not be resolved during verification.', }, ], }, - "empty.dev": { + 'empty.dev': { profile: { - name: "Empty Developer", - avatar: "https://api.dicebear.com/9.x/initials/svg?seed=empty.dev", - description: "A profile with no linked accounts yet.", + name: 'Empty Developer', + avatar: 'https://api.dicebear.com/9.x/initials/svg?seed=empty.dev', + description: 'A profile with no linked accounts yet.', }, accounts: [], }, -}; +} function domainToDisplayName(domain: string): string { - const firstSegment = domain.split(".")[0] || domain; - return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1); + const firstSegment = domain.split('.')[0] || domain + return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1) } function buildFallbackProfile(domain: string): KeytraceResponse { @@ -74,52 +71,52 @@ function buildFallbackProfile(domain: string): KeytraceResponse { }, accounts: [ { - platform: "github", + platform: 'github', username: domain, displayName: domain, avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=github-${encodeURIComponent(domain)}`, url: `https://github.com/${domain}`, - status: "verified", - proofMethod: "github", - addedAt: "2026-04-01T10:00:00.000Z", - lastCheckedAt: "2026-04-21T10:00:00.000Z", + status: 'verified', + proofMethod: 'github', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-21T10:00:00.000Z', }, { - platform: "npm", + platform: 'npm', username: domain, displayName: domain, avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=npm-${encodeURIComponent(domain)}`, url: `https://www.npmjs.com/~${domain}`, - status: "unverified", - proofMethod: "npm", - addedAt: "2026-04-01T10:00:00.000Z", - lastCheckedAt: "2026-04-01T10:00:00.000Z", - failureReason: "Proof exists but has not been verified yet.", + status: 'unverified', + proofMethod: 'npm', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-01T10:00:00.000Z', + failureReason: 'Proof exists but has not been verified yet.', }, { - platform: "mastodon", + platform: 'mastodon', username: `@${domain}@mastodon.social`, displayName: domain, avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=mastodon-${encodeURIComponent(domain)}`, url: `https://mastodon.social/@${encodeURIComponent(domain)}`, - status: "stale", - proofMethod: "mastodon", - addedAt: "2026-04-01T10:00:00.000Z", - lastCheckedAt: "2026-04-08T10:00:00.000Z", - failureReason: "Verification check is out of date.", + status: 'stale', + proofMethod: 'mastodon', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-08T10:00:00.000Z', + failureReason: 'Verification check is out of date.', }, ], - }; + } } -export default defineEventHandler((event) => { - const domain = getRouterParam(event, "domain")?.trim().toLowerCase(); +export default defineEventHandler(event => { + const domain = getRouterParam(event, 'domain')?.trim().toLowerCase() if (!domain) { throw createError({ statusCode: 400, - message: "Domain is required", - }); + message: 'Domain is required', + }) } - return MOCK_KEYTRACE_PROFILES[domain] ?? buildFallbackProfile(domain); -}); + return MOCK_KEYTRACE_PROFILES[domain] ?? buildFallbackProfile(domain) +}) diff --git a/server/api/keytrace/reverify.post.ts b/server/api/keytrace/reverify.post.ts index 9030cd85b2..a85341aa16 100644 --- a/server/api/keytrace/reverify.post.ts +++ b/server/api/keytrace/reverify.post.ts @@ -1,30 +1,27 @@ -import type { - KeytraceReverifyRequest, - KeytraceReverifyResponse, -} from "#shared/types/keytrace"; +import type { KeytraceReverifyRequest, KeytraceReverifyResponse } from '#shared/types/keytrace' -export default defineEventHandler(async (event) => { - const body = await readBody(event); +export default defineEventHandler(async event => { + const body = await readBody(event) - const platform = body?.platform?.trim().toLowerCase(); - const username = body?.username?.trim(); + const platform = body?.platform?.trim().toLowerCase() + const username = body?.username?.trim() if (!platform || !username) { throw createError({ statusCode: 400, - message: "platform and username are required", - }); + message: 'platform and username are required', + }) } - const lastCheckedAt = new Date().toISOString(); + const lastCheckedAt = new Date().toISOString() // TODO: Implement per-platform proof verification for KeytraceReverifyResponse // construction here (including mastodon and npm short-username logic), ideally // via a verifyProofForPlatform(platform, username, proofData) helper. const response: KeytraceReverifyResponse = { - status: "unverified", + status: 'unverified', lastCheckedAt, - failureReason: "Proof verification is not implemented yet.", - }; - return response; -}); + failureReason: 'Proof verification is not implemented yet.', + } + return response +}) diff --git a/shared/types/keytrace.ts b/shared/types/keytrace.ts index 859974ff42..2219e732ad 100644 --- a/shared/types/keytrace.ts +++ b/shared/types/keytrace.ts @@ -1,50 +1,40 @@ export type KeytraceProfile = { - name: string; - avatar: string; - banner?: string; - description: string; -}; + name: string + avatar: string + banner?: string + description: string +} -export type KeytraceVerificationStatus = - | "verified" - | "unverified" - | "stale" - | "failed"; +export type KeytraceVerificationStatus = 'verified' | 'unverified' | 'stale' | 'failed' -export type KeytraceProofMethod = - | "dns" - | "github" - | "npm" - | "mastodon" - | "pgp" - | "other"; +export type KeytraceProofMethod = 'dns' | 'github' | 'npm' | 'mastodon' | 'pgp' | 'other' export type KeytraceAccount = { - platform: string; - username: string; - displayName?: string; - avatar?: string; - url?: string; - status: KeytraceVerificationStatus; - proofMethod: KeytraceProofMethod; - addedAt: string; - lastCheckedAt: string; - failureReason?: string; -}; + platform: string + username: string + displayName?: string + avatar?: string + url?: string + status: KeytraceVerificationStatus + proofMethod: KeytraceProofMethod + addedAt: string + lastCheckedAt: string + failureReason?: string +} export type KeytraceResponse = { - profile: KeytraceProfile; - accounts: KeytraceAccount[]; -}; + profile: KeytraceProfile + accounts: KeytraceAccount[] +} export type KeytraceReverifyRequest = { - platform: string; - username: string; - url?: string; -}; + platform: string + username: string + url?: string +} export type KeytraceReverifyResponse = { - status: KeytraceVerificationStatus; - lastCheckedAt: string; - failureReason?: string; -}; + status: KeytraceVerificationStatus + lastCheckedAt: string + failureReason?: string +} From d44a19971e91c5b372f0466ec0aef1a9ab1c705a Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 13:37:57 +0530 Subject: [PATCH 03/28] test: fix unit test --- test/nuxt/a11y.spec.ts | 63 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/nuxt/a11y.spec.ts b/test/nuxt/a11y.spec.ts index aa16620760..e81af4c8d6 100644 --- a/test/nuxt/a11y.spec.ts +++ b/test/nuxt/a11y.spec.ts @@ -271,6 +271,9 @@ import FacetScatterChart from '~/components/Compare/FacetScatterChart.vue' import PackageLikeCard from '~/components/Package/LikeCard.vue' import SizeIncrease from '~/components/Package/SizeIncrease.vue' import Likes from '~/components/Package/Likes.vue' +import AccountItem from '~/components/AccountItem.vue' +import LinkedAccounts from '~/components/LinkedAccounts.vue' +import ProfileHeader from '~/components/ProfileHeader.vue' import type { VueUiXyDatasetItem } from 'vue-data-ui' describe('component accessibility audits', () => { @@ -904,6 +907,66 @@ describe('component accessibility audits', () => { }) }) + describe('ProfileHeader', () => { + it('should have no accessibility violations with profile data', async () => { + const component = await mountSuspended(ProfileHeader, { + props: { + profile: { + name: 'npmx Team', + avatar: 'https://api.dicebear.com/9.x/shapes/svg?seed=npmx', + description: 'Open source maintainers', + }, + }, + }) + const results = await runAxe(component) + expect(results.violations).toEqual([]) + }) + }) + + describe('LinkedAccounts', () => { + it('should have no accessibility violations with account list', async () => { + const component = await mountSuspended(LinkedAccounts, { + props: { + accounts: [ + { + platform: 'github', + username: 'npmx-dev', + displayName: 'npmx-dev', + status: 'verified', + proofMethod: 'github', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-21T10:00:00.000Z', + url: 'https://github.com/npmx-dev', + }, + ], + }, + }) + const results = await runAxe(component) + expect(results.violations).toEqual([]) + }) + }) + + describe('AccountItem', () => { + it('should have no accessibility violations', async () => { + const component = await mountSuspended(AccountItem, { + props: { + account: { + platform: 'github', + username: 'npmx-dev', + displayName: 'npmx-dev', + status: 'verified', + proofMethod: 'github', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-21T10:00:00.000Z', + url: 'https://github.com/npmx-dev', + }, + }, + }) + const results = await runAxe(component) + expect(results.violations).toEqual([]) + }) + }) + describe('PackageHeader', () => { it('should have no accessibility violations', async () => { const component = await mountSuspended(PackageHeader, { From 67f45f3d2b330380d72dedefc143ffe7e60f0c2e Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 14:57:29 +0530 Subject: [PATCH 04/28] refactor: fix failed components test, remove error translations from locale files --- app/components/AccountItem.vue | 4 ++-- app/components/Compare/FacetSelector.vue | 17 +++++++---------- i18n/locales/ar-EG.json | 1 - i18n/locales/az-AZ.json | 1 - i18n/locales/bg-BG.json | 1 - i18n/locales/cs-CZ.json | 1 - i18n/locales/de.json | 1 - i18n/locales/en.json | 1 - i18n/locales/es.json | 1 - i18n/locales/fr-FR.json | 1 - i18n/locales/hi-IN.json | 1 - i18n/locales/hu-HU.json | 1 - i18n/locales/id-ID.json | 1 - i18n/locales/ja-JP.json | 1 - i18n/locales/mr-IN.json | 1 - i18n/locales/nb-NO.json | 1 - i18n/locales/nl.json | 1 - i18n/locales/pl-PL.json | 1 - i18n/locales/pt-BR.json | 1 - i18n/locales/ru-RU.json | 1 - i18n/locales/sr-Latn-RS.json | 1 - i18n/locales/tr-TR.json | 1 - i18n/locales/uk-UA.json | 1 - i18n/locales/vi-VN.json | 1 - i18n/locales/zh-CN.json | 1 - i18n/locales/zh-TW.json | 1 - 26 files changed, 9 insertions(+), 36 deletions(-) diff --git a/app/components/AccountItem.vue b/app/components/AccountItem.vue index 25ac23b149..5e151bbc83 100644 --- a/app/components/AccountItem.vue +++ b/app/components/AccountItem.vue @@ -229,7 +229,7 @@ function formatDate(value: string): string { diff --git a/app/components/Compare/FacetSelector.vue b/app/components/Compare/FacetSelector.vue index 3a30a603c6..da093c47f3 100644 --- a/app/components/Compare/FacetSelector.vue +++ b/app/components/Compare/FacetSelector.vue @@ -101,24 +101,21 @@ function deselectAllFacet(category: string) { :aria-labelledby="`facet-category-label-${category}`" data-facet-category-facets > - +
diff --git a/i18n/locales/ar-EG.json b/i18n/locales/ar-EG.json index 83ce5e2dcb..19a25e964e 100644 --- a/i18n/locales/ar-EG.json +++ b/i18n/locales/ar-EG.json @@ -82,7 +82,6 @@ "cancel": "إلغاء", "save": "حفظ", "edit": "تعديل", - "error": "خطأ", "view_on": { "gitlab": "عرض على GitLab", "bitbucket": "عرض على Bitbucket", diff --git a/i18n/locales/az-AZ.json b/i18n/locales/az-AZ.json index d21379292f..b2d7fc3897 100644 --- a/i18n/locales/az-AZ.json +++ b/i18n/locales/az-AZ.json @@ -193,7 +193,6 @@ "cancel": "Ləğv et", "save": "Saxla", "edit": "Redaktə et", - "error": "Xəta", "view_on": { "npm": "npm-də bax", "github": "GitHub-da bax" diff --git a/i18n/locales/bg-BG.json b/i18n/locales/bg-BG.json index daa106948f..03ec65790c 100644 --- a/i18n/locales/bg-BG.json +++ b/i18n/locales/bg-BG.json @@ -156,7 +156,6 @@ "cancel": "Отказ", "save": "Запазване", "edit": "Редактиране", - "error": "Грешка", "view_on": { "npm": "преглед в npm", "github": "Преглед в GitHub" diff --git a/i18n/locales/cs-CZ.json b/i18n/locales/cs-CZ.json index 84cbc676be..67bea763db 100644 --- a/i18n/locales/cs-CZ.json +++ b/i18n/locales/cs-CZ.json @@ -323,7 +323,6 @@ "cancel": "Zrušit", "save": "Uložit", "edit": "Upravit", - "error": "Chyba", "view_on": { "npm": "Zobrazit na npm", "github": "Zobrazit na GitHubu", diff --git a/i18n/locales/de.json b/i18n/locales/de.json index 1a29179809..e191cee94b 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -322,7 +322,6 @@ "cancel": "Abbrechen", "save": "Speichern", "edit": "Bearbeiten", - "error": "Fehler", "view_on": { "npm": "Auf npm ansehen", "github": "Auf GitHub ansehen", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 5e31a60a21..924e5eaa74 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -327,7 +327,6 @@ "cancel": "Cancel", "save": "Save", "edit": "Edit", - "error": "Error", "view_on": { "npm": "View on npm", "github": "View on GitHub", diff --git a/i18n/locales/es.json b/i18n/locales/es.json index a723b3e57c..ddce4c624b 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -211,7 +211,6 @@ "cancel": "Cancelar", "save": "Guardar", "edit": "Editar", - "error": "Error", "view_on": { "npm": "ver en npm", "github": "Ver en GitHub", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 01c18381a0..2c814d635a 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -324,7 +324,6 @@ "cancel": "Annuler", "save": "Enregistrer", "edit": "Modifier", - "error": "Erreur", "view_on": { "npm": "voir sur npm", "github": "Voir sur GitHub", diff --git a/i18n/locales/hi-IN.json b/i18n/locales/hi-IN.json index 18391f03c2..dff0bb6943 100644 --- a/i18n/locales/hi-IN.json +++ b/i18n/locales/hi-IN.json @@ -210,7 +210,6 @@ "cancel": "रद्द करें", "save": "सहेजें", "edit": "संपादित करें", - "error": "त्रुटि", "view_on": { "npm": "npm पर देखें", "github": "GitHub पर देखें", diff --git a/i18n/locales/hu-HU.json b/i18n/locales/hu-HU.json index 0b391fa6f2..f7eb646ef1 100644 --- a/i18n/locales/hu-HU.json +++ b/i18n/locales/hu-HU.json @@ -156,7 +156,6 @@ "cancel": "Mégse", "save": "Mentés", "edit": "Szerkesztés", - "error": "Hiba", "view_on": { "npm": "megtekintés npm-en", "github": "Megtekintés GitHubon" diff --git a/i18n/locales/id-ID.json b/i18n/locales/id-ID.json index 717b761a39..73dcae1fdf 100644 --- a/i18n/locales/id-ID.json +++ b/i18n/locales/id-ID.json @@ -211,7 +211,6 @@ "cancel": "Batal", "save": "Simpan", "edit": "Edit", - "error": "Kesalahan", "view_on": { "npm": "lihat di npm", "github": "Lihat di GitHub", diff --git a/i18n/locales/ja-JP.json b/i18n/locales/ja-JP.json index 63e51d154a..69ba897b2c 100644 --- a/i18n/locales/ja-JP.json +++ b/i18n/locales/ja-JP.json @@ -193,7 +193,6 @@ "cancel": "キャンセル", "save": "保存", "edit": "編集", - "error": "エラー", "view_on": { "npm": "npmで表示", "github": "GitHubで表示", diff --git a/i18n/locales/mr-IN.json b/i18n/locales/mr-IN.json index 7bee21b07b..ed80793975 100644 --- a/i18n/locales/mr-IN.json +++ b/i18n/locales/mr-IN.json @@ -194,7 +194,6 @@ "cancel": "रद्द करा", "save": "जतन करा", "edit": "संपादित करा", - "error": "त्रुटी", "view_on": { "npm": "npm वर पहा", "github": "GitHub वर पहा", diff --git a/i18n/locales/nb-NO.json b/i18n/locales/nb-NO.json index 749c355749..1b21b3d59f 100644 --- a/i18n/locales/nb-NO.json +++ b/i18n/locales/nb-NO.json @@ -322,7 +322,6 @@ "cancel": "Avbryt", "save": "Lagre", "edit": "Rediger", - "error": "Feil", "view_on": { "npm": "vis på npm", "github": "Vis på GitHub", diff --git a/i18n/locales/nl.json b/i18n/locales/nl.json index 45634a2503..4b820c60c9 100644 --- a/i18n/locales/nl.json +++ b/i18n/locales/nl.json @@ -322,7 +322,6 @@ "cancel": "Annuleer", "save": "Opslaan", "edit": "Wijzigen", - "error": "Fout", "view_on": { "npm": "Bekijk op npm", "github": "Bekijk op GitHub", diff --git a/i18n/locales/pl-PL.json b/i18n/locales/pl-PL.json index 85cfd34c32..a009daea5b 100644 --- a/i18n/locales/pl-PL.json +++ b/i18n/locales/pl-PL.json @@ -193,7 +193,6 @@ "cancel": "Anuluj", "save": "Zapisz", "edit": "Edytuj", - "error": "Błąd", "view_on": { "npm": "zobacz na npm", "github": "Zobacz w GitHub", diff --git a/i18n/locales/pt-BR.json b/i18n/locales/pt-BR.json index a32f720e61..6e0e05ec30 100644 --- a/i18n/locales/pt-BR.json +++ b/i18n/locales/pt-BR.json @@ -319,7 +319,6 @@ "cancel": "Cancelar", "save": "Salvar", "edit": "Editar", - "error": "Erro", "view_on": { "npm": "Ver no npm", "github": "Ver no GitHub", diff --git a/i18n/locales/ru-RU.json b/i18n/locales/ru-RU.json index bc9adaa933..55bbb22597 100644 --- a/i18n/locales/ru-RU.json +++ b/i18n/locales/ru-RU.json @@ -324,7 +324,6 @@ "cancel": "Отменить", "save": "Сохранить", "edit": "Изменить", - "error": "Ошибка", "view_on": { "npm": "Открыть на npm", "github": "Открыть на GitHub", diff --git a/i18n/locales/sr-Latn-RS.json b/i18n/locales/sr-Latn-RS.json index e7c98216c7..299f8e922d 100644 --- a/i18n/locales/sr-Latn-RS.json +++ b/i18n/locales/sr-Latn-RS.json @@ -211,7 +211,6 @@ "cancel": "Otkažite", "save": "Sačuvajte", "edit": "Uredite", - "error": "Greška", "view_on": { "npm": "pogledajte na npm-u", "github": "Pogledajte na GitHub-u", diff --git a/i18n/locales/tr-TR.json b/i18n/locales/tr-TR.json index 959564eac2..5508d468d3 100644 --- a/i18n/locales/tr-TR.json +++ b/i18n/locales/tr-TR.json @@ -193,7 +193,6 @@ "cancel": "İptal", "save": "Kaydet", "edit": "Düzenle", - "error": "Hata", "view_on": { "npm": "npm'de görüntüle", "github": "GitHub'da görüntüle" diff --git a/i18n/locales/uk-UA.json b/i18n/locales/uk-UA.json index b229774241..cf78807113 100644 --- a/i18n/locales/uk-UA.json +++ b/i18n/locales/uk-UA.json @@ -211,7 +211,6 @@ "cancel": "Скасувати", "save": "Зберегти", "edit": "Редагувати", - "error": "Помилка", "view_on": { "npm": "Переглянути на npm", "github": "Переглянути на GitHub", diff --git a/i18n/locales/vi-VN.json b/i18n/locales/vi-VN.json index 10721d5f92..73950927c7 100644 --- a/i18n/locales/vi-VN.json +++ b/i18n/locales/vi-VN.json @@ -211,7 +211,6 @@ "cancel": "Hủy", "save": "Lưu", "edit": "Chỉnh sửa", - "error": "Lỗi", "view_on": { "npm": "xem trên npm", "github": "Xem trên GitHub", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 9b99aee730..6854700b65 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -323,7 +323,6 @@ "cancel": "取消", "save": "保存", "edit": "编辑", - "error": "加载出错", "view_on": { "npm": "在 npm 上查看", "github": "在 GitHub 上查看", diff --git a/i18n/locales/zh-TW.json b/i18n/locales/zh-TW.json index 7843986523..4b17ac1cb4 100644 --- a/i18n/locales/zh-TW.json +++ b/i18n/locales/zh-TW.json @@ -322,7 +322,6 @@ "cancel": "取消", "save": "儲存", "edit": "編輯", - "error": "錯誤", "view_on": { "npm": "在 npm 上檢視", "github": "在 GitHub 上檢視", From e72233afe7c78a1b74b226798b66fa2b125e9e3d Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 14:59:54 +0530 Subject: [PATCH 05/28] Update i18n schema after removing unused common.error key --- i18n/schema.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/i18n/schema.json b/i18n/schema.json index 3f7d803d9a..a3505b4897 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -985,9 +985,6 @@ "edit": { "type": "string" }, - "error": { - "type": "string" - }, "view_on": { "type": "object", "properties": { @@ -1069,13 +1066,13 @@ "likes": { "type": "string" }, - "likes_error": { + "public_interests_description": { "type": "string" }, - "likes_empty": { + "likes_error": { "type": "string" }, - "public_interests_description": { + "likes_empty": { "type": "string" }, "seo_title": { From 1e2c977de9648c79d114ac2fcbe6b449d5439278 Mon Sep 17 00:00:00 2001 From: Bittu kumar Date: Thu, 23 Apr 2026 15:09:23 +0530 Subject: [PATCH 06/28] Update i18n/locales/de-AT.json Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- i18n/locales/de-AT.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/locales/de-AT.json b/i18n/locales/de-AT.json index daeddeec95..1e748e02cf 100644 --- a/i18n/locales/de-AT.json +++ b/i18n/locales/de-AT.json @@ -24,7 +24,7 @@ }, "profile": { "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", - "likes_error": "Beliebte Pakete konnten nicht geladen werden.", - "likes_empty": "Noch keine beliebten Pakete vorhanden." + "likes_error": "Mit „Gefällt mir" markierte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine mit „Gefällt mir" markierten Pakete." } } From eaf0536790415ed700f18a560fa65267c02d7006 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 20:27:58 +0530 Subject: [PATCH 07/28] fix: failed e2e test --- app/components/AccountItem.vue | 8 ++--- app/components/LinkedAccounts.vue | 49 ++++++++++++++++++++++++------- app/components/ProfileHeader.vue | 4 +-- i18n/locales/en.json | 14 +++++++++ i18n/schema.json | 42 ++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 16 deletions(-) diff --git a/app/components/AccountItem.vue b/app/components/AccountItem.vue index 5e151bbc83..6b747dac50 100644 --- a/app/components/AccountItem.vue +++ b/app/components/AccountItem.vue @@ -167,6 +167,9 @@ async function reverifyAccount() { body, }) + // Attach rejection handler to prevent unhandled promise rejection warnings + responsePromise.catch(() => {}) + await runStep(0) await runStep(1) await runStep(2) @@ -199,10 +202,7 @@ function getStepState(stepIndex: number): 'done' | 'active' | 'idle' { return 'done' } - if ( - currentVerificationStep.value === stepIndex && - (isReverifying.value || !!reverifyError.value) - ) { + if (currentVerificationStep.value === stepIndex && isReverifying.value && !reverifyError.value) { return 'active' } diff --git a/app/components/LinkedAccounts.vue b/app/components/LinkedAccounts.vue index 994be8d3d7..3d2165b2d8 100644 --- a/app/components/LinkedAccounts.vue +++ b/app/components/LinkedAccounts.vue @@ -1,12 +1,27 @@ - - diff --git a/app/pages/profile/[identity]/index.vue b/app/pages/profile/[identity]/index.vue index 805a7d1c37..043d6cde62 100644 --- a/app/pages/profile/[identity]/index.vue +++ b/app/pages/profile/[identity]/index.vue @@ -97,11 +97,7 @@ const inviteUrl = computed(() => { }) const safeProfileWebsiteUrl = computed(() => getSafeHttpUrl(profile.value.website)) -const { - profile: keytraceProfile, - accounts: keytraceAccounts, - loading: keytraceLoading, -} = useKeytraceProfile(identity) +const { accounts: keytraceAccounts, loading: keytraceLoading } = useKeytraceProfile(identity) useCommandPaletteContextCommands( computed((): CommandPaletteContextCommandInput[] => { @@ -238,8 +234,7 @@ defineOgImage( -
- +
({ platform: mapClaimTypeToPlatform(claim.type), username: claim.identity.subject, displayName: claim.identity.displayName || claim.identity.subject, - avatar: claim.identity.avatarUrl || undefined, - url: claim.identity.profileUrl || undefined, + avatar: toAccountAvatarUrl(claim.identity.avatarUrl, imageProxySecret, claim.identity.subject), + url: getSafeHttpUrl(claim.identity.profileUrl) || undefined, status: mapKeytraceVerificationStatus(claim), proofMethod: mapProofMethod(claim.type), addedAt: claim.claim.createdAt, @@ -74,7 +111,7 @@ type KeytraceFetchResult = | { kind: 'error'; error: unknown } // Fetch real Keytrace profile data -async function fetchKeytraceProfile(domain: string): Promise { +async function fetchKeytraceProfile(domain: string, imageProxySecret: string): Promise { try { const result = await getClaimsForHandle(domain) @@ -93,12 +130,10 @@ async function fetchKeytraceProfile(domain: string): Promise { + const { imageProxySecret } = useRuntimeConfig(event) const domain = getRouterParam(event, 'domain')?.trim().toLowerCase() if (!domain) { throw createError({ @@ -117,16 +153,16 @@ export default defineEventHandler(async event => { } // Try to fetch real Keytrace data - const keytraceData = await fetchKeytraceProfile(domain) + const keytraceData = await fetchKeytraceProfile(domain, imageProxySecret) if (keytraceData.kind === 'success') { return keytraceData.data } if (keytraceData.kind === 'no-claims') { - return buildFallbackProfile(domain) + return buildFallbackProfile(domain, imageProxySecret) } // If Keytrace is unavailable and mock mode isn't allowed, return a neutral profile. - return buildServiceUnavailableProfile(domain) + return buildServiceUnavailableProfile(domain, imageProxySecret) }) From 1c96b93b84f659aecf4db26088122140705316eb Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 27 Apr 2026 01:27:37 +0530 Subject: [PATCH 20/28] refactor: simplify avatar URL building and improve function formatting --- server/api/keytrace/[domain].ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/server/api/keytrace/[domain].ts b/server/api/keytrace/[domain].ts index fabea9bd4e..3fe34ded03 100644 --- a/server/api/keytrace/[domain].ts +++ b/server/api/keytrace/[domain].ts @@ -14,8 +14,7 @@ function domainToDisplayName(domain: string): string { } function buildDefaultAvatarUrl(domain: string, imageProxySecret: string): string { - const rawFallbackAvatar = `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(domain)}` - return toProxiedImageUrl(rawFallbackAvatar, imageProxySecret) + return buildSeededAvatarUrl(domain, imageProxySecret) } function buildSeededAvatarUrl(seed: string, imageProxySecret: string): string { @@ -23,7 +22,11 @@ function buildSeededAvatarUrl(seed: string, imageProxySecret: string): string { return toProxiedImageUrl(rawFallbackAvatar, imageProxySecret) } -function toProfileAvatarUrl(rawAvatarUrl: string | undefined, domain: string, imageProxySecret: string): string { +function toProfileAvatarUrl( + rawAvatarUrl: string | undefined, + domain: string, + imageProxySecret: string, +): string { const safeAvatarUrl = getSafeHttpUrl(rawAvatarUrl) if (!safeAvatarUrl) { return buildDefaultAvatarUrl(domain, imageProxySecret) @@ -56,7 +59,10 @@ function buildFallbackProfile(domain: string, imageProxySecret: string): Keytrac } } -function buildServiceUnavailableProfile(domain: string, imageProxySecret: string): KeytraceResponse { +function buildServiceUnavailableProfile( + domain: string, + imageProxySecret: string, +): KeytraceResponse { return { profile: { name: `${domainToDisplayName(domain)} Developer`, @@ -111,7 +117,10 @@ type KeytraceFetchResult = | { kind: 'error'; error: unknown } // Fetch real Keytrace profile data -async function fetchKeytraceProfile(domain: string, imageProxySecret: string): Promise { +async function fetchKeytraceProfile( + domain: string, + imageProxySecret: string, +): Promise { try { const result = await getClaimsForHandle(domain) From 45496f33cb5ae4c1c00ba9d86bb977900e124ea1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 27 Apr 2026 01:38:56 +0530 Subject: [PATCH 21/28] refactor: remove unused ProfileHeader component and related accessibility tests --- i18n/locales/en.json | 2 -- test/nuxt/a11y.spec.ts | 17 ----------------- 2 files changed, 19 deletions(-) diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 8e4c1f6deb..bad836caf6 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -365,8 +365,6 @@ "share_button": "Share on Bluesky", "compose_text": "Hey {'@'}{handle}! Have you checked out npmx.dev yet? It's a browser for the npm registry that's fast, modern, and open-source.\nhttps://npmx.dev" }, - "avatar_alt": "Profile avatar", - "unknown_profile": "Unknown profile", "linked_accounts": { "status": { "verified": "Verified", diff --git a/test/nuxt/a11y.spec.ts b/test/nuxt/a11y.spec.ts index c56fafd720..d483dc8996 100644 --- a/test/nuxt/a11y.spec.ts +++ b/test/nuxt/a11y.spec.ts @@ -273,7 +273,6 @@ import SizeIncrease from '~/components/Package/SizeIncrease.vue' import Likes from '~/components/Package/Likes.vue' import AccountItem from '~/components/AccountItem.vue' import LinkedAccounts from '~/components/LinkedAccounts.vue' -import ProfileHeader from '~/components/ProfileHeader.vue' import type { VueUiXyDatasetItem } from 'vue-data-ui' describe('component accessibility audits', () => { @@ -915,22 +914,6 @@ describe('component accessibility audits', () => { }) }) - describe('ProfileHeader', () => { - it('should have no accessibility violations with profile data', async () => { - const component = await mountSuspended(ProfileHeader, { - props: { - profile: { - name: 'npmx Team', - avatar: 'https://api.dicebear.com/9.x/shapes/svg?seed=npmx', - description: 'Open source maintainers', - }, - }, - }) - const results = await runAxe(component) - expect(results.violations).toEqual([]) - }) - }) - describe('LinkedAccounts', () => { it('should have no accessibility violations with account list', async () => { const component = await mountSuspended(LinkedAccounts, { From 355c60090547e389f25a97ec22d29250a87df85f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 20:10:16 +0000 Subject: [PATCH 22/28] [autofix.ci] apply automated fixes --- i18n/locales/de.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/i18n/locales/de.json b/i18n/locales/de.json index e980559ea2..087ad3abbf 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -349,6 +349,8 @@ "website": "Website", "website_placeholder": "https://beispiel.de", "likes": "Likes", + "likes_error": "Gelikte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine gelikten Pakete.", "seo_title": "{handle} - npmx", "seo_description": "npmx-Profil von {handle}", "not_found": "Profil nicht gefunden", @@ -358,10 +360,6 @@ "share_button": "Auf Bluesky teilen", "compose_text": "Hey {'@'}{handle}! Hast du schon npmx.dev ausprobiert? Es ist ein Browser für die npm Registry, der schnell, modern und Open-Source ist.\nhttps://npmx.dev" }, - "likes_error": "Gelikte Pakete konnten nicht geladen werden.", - "likes_empty": "Noch keine gelikten Pakete.", - "avatar_alt": "Profilavatar", - "unknown_profile": "Unbekanntes Profil", "linked_accounts": { "status": { "verified": "Verifiziert", @@ -394,6 +392,7 @@ "size": "Installationsgröße um {percent} gestiegen ({size} größer)", "deps": "{count} zusätzliche Abhängigkeiten" }, + "size_decrease": {}, "replacement": { "title": "Du brauchst diese Abhängigkeit vielleicht nicht.", "native": "Dies kann durch {replacement} ersetzt werden, verfügbar seit Node {nodeVersion}.", @@ -571,6 +570,7 @@ "current_tags": "Aktuelle Tags", "no_match_filter": "Keine Versionen entsprechen {filter}" }, + "timeline": {}, "dependencies": { "title": "Abhängigkeit ({count}) | Abhängigkeiten ({count})", "list_label": "Paketabhängigkeiten", @@ -1331,7 +1331,10 @@ "vulnerabilities": { "label": "Sicherheitslücken", "description": "Bekannte Sicherheitsrisiken" - } + }, + "githubStars": {}, + "githubIssues": {}, + "createdAt": {} }, "values": { "any": "Beliebig", From aa3bd18c848e04934f900f8c2a7cefb605206652 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 27 Apr 2026 01:43:34 +0530 Subject: [PATCH 23/28] refactor: remove unused avatar_alt and unknown_profile properties from schema --- i18n/schema.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/i18n/schema.json b/i18n/schema.json index b6b1689dc9..2bcf4101d2 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -1099,12 +1099,6 @@ }, "additionalProperties": false }, - "avatar_alt": { - "type": "string" - }, - "unknown_profile": { - "type": "string" - }, "linked_accounts": { "type": "object", "properties": { From 9e066c487398232589e67a3daba1ff36de1dce2a Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 4 Jun 2026 19:48:40 +0530 Subject: [PATCH 24/28] fix: correct likes_empty message formatting in bn-IN and nl locales --- i18n/locales/bn-IN.json | 2 +- i18n/locales/nl.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/i18n/locales/bn-IN.json b/i18n/locales/bn-IN.json index 6ba8ba806b..7603513191 100644 --- a/i18n/locales/bn-IN.json +++ b/i18n/locales/bn-IN.json @@ -354,7 +354,7 @@ }, "profile": { "likes_error": "পছন্দের প্যাকেজগুলি লোড করা যায়নি।", - "likes_empty": "এখনও কোনো পছন্দের প্যাকেজ নেই।" + "likes_empty": "এখনও কোনো পছন্দের প্যাকেজ নেই।", "display_name": "প্রদর্শনের নাম", "description": "বর্ণনা", "no_description": "কোনো বর্ণনা দেওয়া হয়নি", diff --git a/i18n/locales/nl.json b/i18n/locales/nl.json index 1ce4e7ab8c..13db86af5a 100644 --- a/i18n/locales/nl.json +++ b/i18n/locales/nl.json @@ -365,11 +365,10 @@ "invite": { "message": "Het lijkt erop dat ze npmx nog niet gebruiken. Wilt u ze hierop wijzen", "share_button": "Deel op Bluesky", + "compose_text": "Hallo {'@'}{handle}! Hebt u npmx.dev al bekeken? Het is een browser voor het npm-register die snel, modern en open source is.\nhttps://npmx.dev" }, "likes_error": "Kon gelikete pakketten niet laden.", "likes_empty": "Nog geen gelikete pakketten." - "compose_text": "Hallo {'@'}{handle}! Hebt u npmx.dev al bekeken? Het is een browser voor het npm-register die snel, modern en open source is.\nhttps://npmx.dev" - } }, "package": { "not_found": "Pakket niet gevonden", From 1463b4951151909773c69356285e6c40f566346d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:20:22 +0000 Subject: [PATCH 25/28] [autofix.ci] apply automated fixes --- i18n/locales/bn-IN.json | 324 ++++++++++++++++++++-------------------- i18n/locales/pt-PT.json | 14 +- i18n/locales/ro-RO.json | 4 +- 3 files changed, 177 insertions(+), 165 deletions(-) diff --git a/i18n/locales/bn-IN.json b/i18n/locales/bn-IN.json index 7603513191..ce6cfbbcc8 100644 --- a/i18n/locales/bn-IN.json +++ b/i18n/locales/bn-IN.json @@ -13,11 +13,11 @@ "trademark_disclaimer": "npm, npm, Inc. এর একটি নিবন্ধিত ট্রেডমার্ক। এই সাইটটি npm, Inc. এর সাথে সংযুক্ত নয়।", "footer": { "about": "আমাদের সম্পর্কে", + "blog": "ব্লগ", "docs": "ডকুমেন্টেশন", "source": "সোর্স", "social": "সোশ্যাল", "chat": "চ্যাট", - "blog": "ব্লগ", "builders_chat": "নির্মাতাদের চ্যাট", "keyboard_shortcuts": "কীবোর্ড শর্টকাটস", "brand": "ব্র্যান্ড", @@ -331,7 +331,6 @@ "cancel": "বাতিল করুন", "save": "সংরক্ষণ করুন", "edit": "সম্পাদনা করুন", - "error": "ত্রুটি", "view_on": { "npm": "npm এ দেখুন", "github": "GitHub এ দেখুন", @@ -353,14 +352,14 @@ "expand_with_name": "{name} বিস্তৃত করুন" }, "profile": { - "likes_error": "পছন্দের প্যাকেজগুলি লোড করা যায়নি।", - "likes_empty": "এখনও কোনো পছন্দের প্যাকেজ নেই।", "display_name": "প্রদর্শনের নাম", "description": "বর্ণনা", "no_description": "কোনো বর্ণনা দেওয়া হয়নি", "website": "ওয়েবসাইট", "website_placeholder": "https://example.com", "likes": "পছন্দ", + "likes_error": "পছন্দের প্যাকেজগুলি লোড করা যায়নি।", + "likes_empty": "এখনও কোনো পছন্দের প্যাকেজ নেই।", "seo_title": "{handle} - npmx", "seo_description": "{handle} দ্বারা npmx প্রোফাইল", "not_found": "প্রোফাইল পাওয়া যায়নি", @@ -369,6 +368,9 @@ "message": "তারা এখনও npmx ব্যবহার করছে বলে মনে হচ্ছে না। এটা সম্পর্কে তাদের বলতে চান?", "share_button": "Bluesky এ শেয়ার করুন", "compose_text": "আরে {'@'}{handle}! আপনি কি এখনও npmx.dev চেক আউট করেছেন? এটি npm রেজিস্ট্রির জন্য একটি ব্রাউজার যা দ্রুত, আধুনিক এবং ওপেন সোর্স।\nhttps://npmx.dev" + }, + "linked_accounts": { + "status": {} } }, "package": { @@ -464,10 +466,10 @@ "top_rank_link_label": "পছন্দ লিডারবোর্ড দেখুন. এই প্যাকেজটি #{rank} র‌্যাঙ্ক করা হয়েছে." }, "docs": { - "not_available": "ডকুমেন্টেশন উপলব্ধ নেই", - "not_available_detail": "আমরা এই ভার্সনের জন্য ডকুমেন্টেশন তৈরি করতে পারিনি।", "contents": "বিষয়বস্তু", "default_not_available": "এই ভার্সনের জন্য ডকুমেন্টেশন উপলব্ধ নয়।", + "not_available": "ডকুমেন্টেশন উপলব্ধ নেই", + "not_available_detail": "আমরা এই ভার্সনের জন্য ডকুমেন্টেশন তৈরি করতে পারিনি।", "page_title": "API ডকুমেন্টেশন - npmx", "page_title_name": "{name} ডকুমেন্টেশন - npmx", "page_title_version": "{name} ডকুমেন্টেশন - npmx", @@ -478,9 +480,9 @@ "title": "শুরু করুন", "pm_label": "প্যাকেজ ম্যানেজার", "copy_command": "ইনস্টল কমান্ড কপি করুন", - "view_types": "{package} দেখুন", "copy_dev_command": "ডেভ ইনস্টল কমান্ড কপি করুন", - "dev_dependency_hint": "সাধারণত ডেভ ডিপেনডেন্সি হিসেবে ইনস্টল করা হয়" + "dev_dependency_hint": "সাধারণত ডেভ ডিপেনডেন্সি হিসেবে ইনস্টল করা হয়", + "view_types": "{package} দেখুন" }, "create": { "title": "নতুন প্রকল্প তৈরি করুন", @@ -494,6 +496,7 @@ "readme": { "title": "রিডমি", "no_readme": "README উপলব্ধ নেই।", + "toc_title": "আউটলাইন", "callout": { "note": "দ্রষ্টব্য", "tip": "টিপ", @@ -501,7 +504,6 @@ "warning": "সতর্কতা", "caution": "সতর্কতা" }, - "toc_title": "আউটলাইন", "copy_as_markdown": "মার্কডাউন হিসাবে README কপি করুন", "error_loading": "README বিশদ লোড করতে ব্যর্থ হয়েছে৷" }, @@ -530,10 +532,10 @@ "compatibility": "সামঞ্জস্য", "card": { "publisher": "প্রকাশক", + "published": "প্রকাশিত", "weekly_downloads": "সাপ্তাহিক ডাউনলোড", "keywords": "কীওয়ার্ড", "license": "লাইসেন্স", - "published": "প্রকাশিত", "select": "প্যাকেজ নির্বাচন করুন", "select_maximum": "সর্বোচ্চ {count}টি প্যাকেজ নির্বাচন করা যাবে" }, @@ -550,10 +552,6 @@ "all_covered": "সব ভার্সন উপরের ট্যাগ দ্বারা আবৃত", "deprecated_title": "{version} (নিষ্ক্রিয়)", "view_all": "{count}টি ভার্সন দেখুন | সব {count}টি ভার্সন দেখুন", - "copy_alt": { - "per_version_analysis": "{version} ভার্সনটি {downloads} বার ডাউনলোড করা হয়েছে", - "general_description": "{package_name} প্যাকেজের {versions_count}টি {semver_grouping_mode} ভার্সনের প্রতি-ভার্সন ডাউনলোড দেখানো বার চার্ট, {first_version} ভার্সন থেকে {last_version} ভার্সন পর্যন্ত, {date_range_label} সময়সীমায়। সবচেয়ে বেশি ডাউনলোড হওয়া ভার্সন হলো {max_downloaded_version}, যার ডাউনলোড সংখ্যা {max_version_downloads}। {per_version_analysis}। {watermark}।" - }, "view_all_versions": "সব ভার্সন দেখুন", "distribution_title": "Semver গ্রুপ", "distribution_modal_title": "ভার্সনসমূহ", @@ -581,10 +579,43 @@ "license_change_warning": "আগের ভার্সনের পর থেকে লাইসেন্স পরিবর্তিত হয়েছে।", "license_change_record": "এই প্যাকেজের লাইসেন্স \"{from}\" থেকে \"{to}\" তে পরিবর্তিত হয়েছে।", "no_matches": "এই পরিসরের সাথে কোনো ভার্সন মেলেনি", + "copy_alt": { + "per_version_analysis": "{version} ভার্সনটি {downloads} বার ডাউনলোড করা হয়েছে", + "general_description": "{package_name} প্যাকেজের {versions_count}টি {semver_grouping_mode} ভার্সনের প্রতি-ভার্সন ডাউনলোড দেখানো বার চার্ট, {first_version} ভার্সন থেকে {last_version} ভার্সন পর্যন্ত, {date_range_label} সময়সীমায়। সবচেয়ে বেশি ডাউনলোড হওয়া ভার্সন হলো {max_downloaded_version}, যার ডাউনলোড সংখ্যা {max_version_downloads}। {per_version_analysis}। {watermark}।" + }, "page_title": "ভার্সন ইতিহাস", "current_tags": "বর্তমান ট্যাগসমূহ", "no_match_filter": "{filter} এর সাথে কোনো ভার্সন মেলেনি" }, + "timeline": { + "load_more": "আরো লোড করুন", + "load_error": "টাইমলাইন লোড করা যায়নি। অনুগ্রহ করে পরে আবার চেষ্টা করুন।", + "size_increase": "ইনস্টল সাইজ {percent}% বৃদ্ধি পেয়েছে ({size})", + "size_decrease": "ইনস্টল সাইজ {percent}% কমেছে ({size})", + "dep_increase": "{count}টি ডিপেনডেন্সি যোগ হয়েছে", + "dep_decrease": "{count}টি ডিপেনডেন্সি সরানো হয়েছে", + "license_change": "লাইসেন্স {from} থেকে {to} এ পরিবর্তিত হয়েছে", + "esm_added": "মডিউল টাইপ ESM এ পরিবর্তিত হয়েছে", + "esm_removed": "মডিউল টাইপ ESM থেকে CJS এ পরিবর্তিত হয়েছে", + "types_added": "TypeScript টাইপ যোগ করা হয়েছে", + "types_removed": "TypeScript টাইপ সরানো হয়েছে", + "trusted_publisher_added": "বিশ্বস্ত প্রকাশনা সক্রিয় করা হয়েছে", + "trusted_publisher_removed": "বিশ্বস্ত প্রকাশনা সরানো হয়েছে", + "provenance_added": "প্রোভেন্যান্স সক্রিয় করা হয়েছে", + "provenance_removed": "প্রোভেন্যান্স সরানো হয়েছে", + "chart": { + "tab_aria_label": "মেট্রিক নির্বাচন", + "base_scale": "y-অক্ষ শূন্য থেকে শুরু করুন", + "zoom": "জুম", + "reset_minimap": "মিনিম্যাপ রিসেট করুন", + "ordered_versions": "শুধুমাত্র স্থিতিশীল", + "copy_alt": { + "key_changes": "মূল পরিবর্তনসমূহ: {version_events}।", + "version_events": "ভার্সন {version}: {events}", + "general_description": "{package} প্যাকেজের {metric} প্রদর্শনকারী লাইন চার্ট, {first} ভার্সন থেকে {last} ভার্সন পর্যন্ত। {first} ভার্সনে {metric} এর মান {first_value}, এবং {last} ভার্সনে {last_value} ({overall_progress_percentage}% সামগ্রিক পরিবর্তন)। {key_changes} {watermark}।" + } + } + }, "dependencies": { "title": "নির্ভরতা ({count})", "list_label": "প্যাকেজ নির্ভরতা", @@ -623,6 +654,15 @@ "maintainer_template": "{avatar} {char126}{name}" }, "trends": { + "chart_assistive_text": { + "keyboard_navigation_horizontal": "ডেটা পয়েন্টগুলোর মধ্যে যেতে বাম এবং ডান অ্যারো কী ব্যবহার করুন।", + "keyboard_navigation_vertical": "ডেটা পয়েন্টগুলোর মধ্যে যেতে উপরে এবং নিচের অ্যারো কী ব্যবহার করুন।", + "table_available": "এই চার্টের জন্য একটি ডেটা টেবিল নিচে উপলব্ধ রয়েছে।", + "table_caption": "চার্ট ডেটা টেবিল" + }, + "chart_view_toggle": "ভিউ পরিবর্তন করুন", + "chart_view_combined": "সমন্বিত ভিউ", + "chart_view_split": "বিভক্ত ভিউ", "granularity": "বিস্তৃতি", "granularity_daily": "দৈনিক", "granularity_weekly": "সাপ্তাহিক", @@ -635,36 +675,6 @@ "date_range_multiline": "{start}\nথেকে {end}", "download_file": "{fileType} ডাউনলোড করুন", "toggle_annotator": "অ্যানোটেটর টগল করুন", - "items": { - "downloads": "ডাউনলোড", - "likes": "পছন্দ", - "contributors": "অবদানকারী" - }, - "copy_alt": { - "trend_none": "মূলত সমতল", - "trend_strong": "শক্তিশালী", - "trend_weak": "দুর্বল", - "trend_undefined": "অনির্ধারিত (অপর্যাপ্ত ডেটা)", - "button_label": "বিকল্প টেক্সট কপি করুন", - "watermark": "নিচে একটি ওয়াটারমার্কে লেখা আছে \"./npmx a fast, modern browser for the npm registry\"", - "analysis": "{package_name} এর শুরু {start_value} থেকে এবং শেষ {end_value} এ, যা প্রতি সময়সীমায় {downloads_slope} ডাউনলোডের ঢালসহ একটি {trend} প্রবণতা দেখায়", - "estimation": "চূড়ান্ত মানটি বর্তমান সময়সীমার আংশিক ডেটার উপর ভিত্তি করে একটি অনুমান।", - "estimations": "চূড়ান্ত মানগুলো বর্তমান সময়সীমার আংশিক ডেটার উপর ভিত্তি করে অনুমান করা হয়েছে।", - "compare": "{packages} এর জন্য প্যাকেজ ডাউনলোড তুলনামূলক লাইন চার্ট।", - "single_package": "{package} প্যাকেজের ডাউনলোড লাইন চার্ট।", - "general_description": "Y অক্ষ ডাউনলোডের সংখ্যা নির্দেশ করে। X অক্ষ {start_date} থেকে {end_date} পর্যন্ত তারিখের পরিসর নির্দেশ করে, যেখানে সময়কাল হলো {granularity}। {estimation_notice} {packages_analysis}। {watermark}।", - "facet_bar_general_description": "{packages} এর জন্য অনুভূমিক বার চার্ট, যেখানে {facet} ({description}) তুলনা করা হয়েছে। {facet_analysis} {watermark}।", - "facet_bar_analysis": "{package_name} এর মান {value}।" - }, - "chart_assistive_text": { - "keyboard_navigation_horizontal": "ডেটা পয়েন্টগুলোর মধ্যে যেতে বাম এবং ডান অ্যারো কী ব্যবহার করুন।", - "keyboard_navigation_vertical": "ডেটা পয়েন্টগুলোর মধ্যে যেতে উপরে এবং নিচের অ্যারো কী ব্যবহার করুন।", - "table_available": "এই চার্টের জন্য একটি ডেটা টেবিল নিচে উপলব্ধ রয়েছে।", - "table_caption": "চার্ট ডেটা টেবিল" - }, - "chart_view_toggle": "ভিউ পরিবর্তন করুন", - "chart_view_combined": "সমন্বিত ভিউ", - "chart_view_split": "বিভক্ত ভিউ", "toggle_stack_mode": "স্ট্যাক মোড পরিবর্তন করুন", "open_options": "অপশন খুলুন", "close_options": "অপশন বন্ধ করুন", @@ -674,6 +684,11 @@ "facet": "বিভাগ", "title": "প্রবণতা", "contributors_skip": "Contributors-এ দেখানো হয়নি (GitHub রিপোজিটরি নেই):", + "items": { + "downloads": "ডাউনলোড", + "likes": "পছন্দ", + "contributors": "অবদানকারী" + }, "data_correction": "ডেটা সংশোধন", "average_window": "গড় উইন্ডো", "smoothing": "মসৃণকরণ", @@ -685,7 +700,23 @@ "known_anomalies_range_named": "{packageName}: {start} থেকে {end}", "known_anomalies_none": "এই প্যাকেজের জন্য কোনো পরিচিত অস্বাভাবিকতা নেই। | এই প্যাকেজগুলোর জন্য কোনো পরিচিত অস্বাভাবিকতা নেই।", "known_anomalies_contribute": "অস্বাভাবিকতার ডেটা যোগ করুন", - "apply_correction": "সংশোধন প্রয়োগ করুন" + "apply_correction": "সংশোধন প্রয়োগ করুন", + "copy_alt": { + "trend_none": "মূলত সমতল", + "trend_strong": "শক্তিশালী", + "trend_weak": "দুর্বল", + "trend_undefined": "অনির্ধারিত (অপর্যাপ্ত ডেটা)", + "button_label": "বিকল্প টেক্সট কপি করুন", + "watermark": "নিচে একটি ওয়াটারমার্কে লেখা আছে \"./npmx a fast, modern browser for the npm registry\"", + "analysis": "{package_name} এর শুরু {start_value} থেকে এবং শেষ {end_value} এ, যা প্রতি সময়সীমায় {downloads_slope} ডাউনলোডের ঢালসহ একটি {trend} প্রবণতা দেখায়", + "estimation": "চূড়ান্ত মানটি বর্তমান সময়সীমার আংশিক ডেটার উপর ভিত্তি করে একটি অনুমান।", + "estimations": "চূড়ান্ত মানগুলো বর্তমান সময়সীমার আংশিক ডেটার উপর ভিত্তি করে অনুমান করা হয়েছে।", + "compare": "{packages} এর জন্য প্যাকেজ ডাউনলোড তুলনামূলক লাইন চার্ট।", + "single_package": "{package} প্যাকেজের ডাউনলোড লাইন চার্ট।", + "general_description": "Y অক্ষ ডাউনলোডের সংখ্যা নির্দেশ করে। X অক্ষ {start_date} থেকে {end_date} পর্যন্ত তারিখের পরিসর নির্দেশ করে, যেখানে সময়কাল হলো {granularity}। {estimation_notice} {packages_analysis}। {watermark}।", + "facet_bar_general_description": "{packages} এর জন্য অনুভূমিক বার চার্ট, যেখানে {facet} ({description}) তুলনা করা হয়েছে। {facet_analysis} {watermark}।", + "facet_bar_analysis": "{package_name} এর মান {value}।" + } }, "downloads": { "title": "সাপ্তাহিক ডাউনলোড", @@ -707,11 +738,11 @@ "esm": "ES Modules সমর্থন আছে", "cjs": "CommonJS সমর্থন আছে", "no_esm": "ES Modules সমর্থন নেই", + "wasm": "WebAssembly রয়েছে", "types_label": "টাইপ", "types_included": "টাইপ অন্তর্ভুক্ত", "types_available": "টাইপ {package} এর মাধ্যমে উপলব্ধ", - "no_types": "TypeScript টাইপ নেই", - "wasm": "WebAssembly রয়েছে" + "no_types": "TypeScript টাইপ নেই" }, "license": { "view_spdx": "SPDX এ লাইসেন্স টেক্সট দেখুন", @@ -773,9 +804,9 @@ }, "sort": { "downloads": "সবচেয়ে বেশি ডাউনলোডকৃত", + "published": "সম্প্রতি প্রকাশিত", "name_asc": "নাম (A-Z)", - "name_desc": "নাম (Z-A)", - "published": "সম্প্রতি প্রকাশিত" + "name_desc": "নাম (Z-A)" }, "size": { "b": "{size} B", @@ -785,35 +816,16 @@ "download": { "button": "ডাউনলোড", "tarball": ".tar.gz হিসেবে টারবল ডাউনলোড করুন" - }, - "timeline": { - "load_more": "আরো লোড করুন", - "load_error": "টাইমলাইন লোড করা যায়নি। অনুগ্রহ করে পরে আবার চেষ্টা করুন।", - "size_increase": "ইনস্টল সাইজ {percent}% বৃদ্ধি পেয়েছে ({size})", - "size_decrease": "ইনস্টল সাইজ {percent}% কমেছে ({size})", - "dep_increase": "{count}টি ডিপেনডেন্সি যোগ হয়েছে", - "dep_decrease": "{count}টি ডিপেনডেন্সি সরানো হয়েছে", - "license_change": "লাইসেন্স {from} থেকে {to} এ পরিবর্তিত হয়েছে", - "esm_added": "মডিউল টাইপ ESM এ পরিবর্তিত হয়েছে", - "esm_removed": "মডিউল টাইপ ESM থেকে CJS এ পরিবর্তিত হয়েছে", - "types_added": "TypeScript টাইপ যোগ করা হয়েছে", - "types_removed": "TypeScript টাইপ সরানো হয়েছে", - "trusted_publisher_added": "বিশ্বস্ত প্রকাশনা সক্রিয় করা হয়েছে", - "trusted_publisher_removed": "বিশ্বস্ত প্রকাশনা সরানো হয়েছে", - "provenance_added": "প্রোভেন্যান্স সক্রিয় করা হয়েছে", - "provenance_removed": "প্রোভেন্যান্স সরানো হয়েছে", - "chart": { - "tab_aria_label": "মেট্রিক নির্বাচন", - "base_scale": "y-অক্ষ শূন্য থেকে শুরু করুন", - "zoom": "জুম", - "reset_minimap": "মিনিম্যাপ রিসেট করুন", - "ordered_versions": "শুধুমাত্র স্থিতিশীল", - "copy_alt": { - "key_changes": "মূল পরিবর্তনসমূহ: {version_events}।", - "version_events": "ভার্সন {version}: {events}", - "general_description": "{package} প্যাকেজের {metric} প্রদর্শনকারী লাইন চার্ট, {first} ভার্সন থেকে {last} ভার্সন পর্যন্ত। {first} ভার্সনে {metric} এর মান {first_value}, এবং {last} ভার্সনে {last_value} ({overall_progress_percentage}% সামগ্রিক পরিবর্তন)। {key_changes} {watermark}।" - } - } + } + }, + "leaderboard": { + "likes": { + "title": "লাইক লিডারবোর্ড", + "description": "বর্তমানে npmx-এর সবচেয়ে বেশি লাইক পাওয়া ১০টি প্যাকেজ।", + "rank": "র‍্যাঙ্ক", + "likes": "লাইক", + "unavailable_title": "এখনও কোনো লাইক লিডারবোর্ড নেই", + "unavailable_description": "এই মুহূর্তে দেখানোর মতো কোনো লাইক লিডারবোর্ড নেই।" } }, "connector": { @@ -851,7 +863,9 @@ "otp_placeholder": "OTP কোড দিন...", "otp_label": "একবার ব্যবহারের পাসওয়ার্ড", "retry_otp": "OTP দিয়ে আবার চেষ্টা করুন", + "retry_web_auth": "ওয়েব অথ দিয়ে আবার চেষ্টা করুন", "retrying": "আবার চেষ্টা করা হচ্ছে...", + "open_web_auth": "ওয়েব অথ লিংক খুলুন", "approve_operation": "অপারেশন অনুমোদন করুন", "remove_operation": "অপারেশন সরান", "approve_all": "সব অনুমোদন করুন", @@ -859,9 +873,7 @@ "executing": "চালানো হচ্ছে...", "log": "লগ", "log_label": "সম্পূর্ণ অপারেশন লগ", - "remove_from_log": "লগ থেকে সরান", - "retry_web_auth": "ওয়েব অথ দিয়ে আবার চেষ্টা করুন", - "open_web_auth": "ওয়েব অথ লিংক খুলুন" + "remove_from_log": "লগ থেকে সরান" } }, "org": { @@ -975,6 +987,7 @@ "invalid_name": "অবৈধ প্যাকেজ নাম:", "available": "এই নামটি উপলব্ধ!", "taken": "এই নামটি ইতিমধ্যে নেওয়া হয়েছে।", + "missing_permission": "আপনার কাছে {'@'}{scope} স্কোপে প্যাকেজ যোগ করার অনুমতি নেই।", "similar_warning": "সমান প্যাকেজ রয়েছে - npm এই নামটি প্রত্যাখ্যান করতে পারে:", "related": "সংশ্লিষ্ট প্যাকেজ:", "scope_warning_title": "পরিবর্তে স্কোপড প্যাকেজ ব্যবহার বিবেচনা করুন", @@ -987,8 +1000,7 @@ "publishing": "প্রকাশ করা হচ্ছে...", "checking": "প্রাপ্যতা যাচাই করা হচ্ছে...", "failed_to_check": "নামের প্রাপ্যতা যাচাই করতে ব্যর্থ", - "failed_to_claim": "প্যাকেজ দাবি করতে ব্যর্থ", - "missing_permission": "আপনার কাছে {'@'}{scope} স্কোপে প্যাকেজ যোগ করার অনুমতি নেই।" + "failed_to_claim": "প্যাকেজ দাবি করতে ব্যর্থ" } }, "code": { @@ -997,8 +1009,12 @@ "lines": "{count}টি লাইন", "toggle_tree": "ফাইল ট্রি টগল করুন", "close_tree": "ফাইল ট্রি বন্ধ করুন", + "copy_content": "ফাইল কন্টেন্ট কপি করুন", "copy_link": "লিঙ্ক কপি করুন", "view_raw": "রও ফাইল দেখুন", + "toggle_container": "কোড কন্টেইনার প্রস্থ টগল করুন", + "open_raw_file": "রও ফাইল খুলুন", + "open_path_dropdown": "পাথ সেগমেন্টস ড্রপডাউন খুলুন", "file_too_large": "প্রিভিউ-র জন্য ফাইল খুব বড়", "file_size_warning": "{size} সিনট্যাক্স হাইলাইটিং-এর জন্য 500KB সীমার চেয়ে বেশি", "failed_to_load": "ফাইল লোড করতে ব্যর্থ", @@ -1017,10 +1033,6 @@ "code": "কোড" }, "file_path": "ফাইল পাথ", - "copy_content": "ফাইল কন্টেন্ট কপি করুন", - "toggle_container": "কোড কন্টেইনার প্রস্থ টগল করুন", - "open_raw_file": "রও ফাইল খুলুন", - "open_path_dropdown": "পাথ সেগমেন্টস ড্রপডাউন খুলুন", "binary_file": "বাইনারি ফাইল", "binary_rendering_warning": "ফাইল টাইপ \"{contentType}\" প্রিভিউয়ের জন্য সমর্থিত নয়." }, @@ -1084,6 +1096,8 @@ "secure": "সতর্কতা ছাড়া", "insecure": "সতর্কতা সহ" }, + "view_selected": "নির্বাচিত দেখুন", + "clear_selected_label": "নির্বাচিত সাফ করুন", "sort": { "label": "প্যাকেজগুলি সাজান", "toggle_direction": "ক্রম দিক টগল করুন", @@ -1094,8 +1108,8 @@ "downloads_day": "ডাউনলোড/দিন", "downloads_month": "ডাউনলোড/মাস", "downloads_year": "ডাউনলোড/বছর", - "name": "নাম", - "published": "সর্বশেষ প্রকাশিত" + "published": "সর্বশেষ প্রকাশিত", + "name": "নাম" }, "columns": { "title": "কলাম", @@ -1106,10 +1120,10 @@ "version": "ভার্সন", "description": "বর্ণনা", "downloads": "ডাউনলোড/সপ্তাহ", + "published": "সর্বশেষ প্রকাশিত", "maintainers": "রক্ষণাবেক্ষণকারী", "keywords": "কীওয়ার্ড", "security": "নিরাপত্তা", - "published": "সর্বশেষ প্রকাশিত", "selection": "প্যাকেজ নির্বাচন করুন" }, "view_mode": { @@ -1137,9 +1151,7 @@ "security_warning": "নিরাপত্তা সতর্কতা", "secure": "নিরাপদ", "no_packages": "কোনো প্যাকেজ পাওয়া যায়নি" - }, - "view_selected": "নির্বাচিত দেখুন", - "clear_selected_label": "নির্বাচিত সাফ করুন" + } }, "about": { "title": "আমাদের সম্পর্কে", @@ -1272,11 +1284,11 @@ "section_packages": "প্যাকেজ", "section_facets": "ফ্যাসেট", "section_comparison": "তুলনা", + "copy_as_markdown": "মার্কডাউন হিসেবে কপি করুন", "loading": "প্যাকেজ ডেটা লোড হচ্ছে...", "error": "প্যাকেজ ডেটা লোড করতে ব্যর্থ। অনুগ্রহ করে আবার চেষ্টা করুন।", "empty_title": "তুলনা করার জন্য প্যাকেজ নির্বাচন করুন", "empty_description": "তাদের মেট্রিক্স পাশাপাশি তুলনা দেখতে উপরে কমপক্ষে ২টি প্যাকেজ অনুসন্ধান করে যোগ করুন।", - "copy_as_markdown": "মার্কডাউন হিসেবে কপি করুন", "table_view": "টেবিল", "charts_view": "চার্ট", "no_chartable_data": "নির্বাচিত ফ্যাসেটগুলোর জন্য কোনো চার্টযোগ্য ডেটা উপলব্ধ নেই।", @@ -1292,6 +1304,17 @@ "packages_selected": "{count}/{max}টি প্যাকেজ নির্বাচিত।", "add_hint": "তুলনা করার জন্য কমপক্ষে ২টি প্যাকেজ যোগ করুন।" }, + "scatter_chart": { + "title": "{x} বনাম {y} তুলনা করুন", + "freshness_score": "নতুনত্ব স্কোর", + "copy_alt": { + "analysis": "{package}: {x_name} ({x_value}) এবং {y_name} ({y_value})", + "description": "{packages}টি প্যাকেজের জন্য {x_name} বনাম {y_name} এর স্ক্যাটার প্লট চার্ট। {analysis}. {watermark}" + }, + "filename": "{x}-vs-{y}-scatter-chart", + "x_axis": "X-অক্ষ ↦", + "y_axis": "Y-অক্ষ ↥" + }, "no_dependency": { "label": "(কোনো নির্ভরতা নেই)", "typeahead_title": "জেমস কী করতেন?", @@ -1304,9 +1327,14 @@ "facets": { "all": "সব", "none": "কিছু না", + "select_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন করুন", + "deselect_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন বাতিল করুন", + "selected_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন করা হয়েছে", + "deselected_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন বাতিল করা হয়েছে", "coming_soon": "শীঘ্রই আসছে", "select_all": "সব ফ্যাসেট নির্বাচন করুন", "deselect_all": "সব ফ্যাসেট অনির্বাচিত করুন", + "binary_only_tooltip": "এই প্যাকেজটি বাইনারি সরবরাহ করে এবং কোনো এক্সপোর্ট নেই", "categories": { "performance": "কর্মক্ষমতা", "health": "স্বাস্থ্য", @@ -1392,39 +1420,7 @@ }, "trends": { "title": "প্রবণতা তুলনা করুন" - }, - "select_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন করুন", - "deselect_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন বাতিল করুন", - "selected_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন করা হয়েছে", - "deselected_all_category_facets": "{category}-এর সব ফ্যাসেট নির্বাচন বাতিল করা হয়েছে", - "binary_only_tooltip": "এই প্যাকেজটি বাইনারি সরবরাহ করে এবং কোনো এক্সপোর্ট নেই" - }, - "version_invalid_url_format": { - "hint": "অবৈধ তুলনা URL। এই ফরম্যাট ব্যবহার করুন: {0}", - "from_version": "থেকে", - "to_version": "পর্যন্ত" - }, - "file_filter_option": { - "all": "সব ({count})", - "added": "যোগ করা হয়েছে ({count})", - "removed": "মুছে ফেলা হয়েছে ({count})", - "modified": "পরিবর্তিত ({count})" - }, - "filter": { - "added": "যোগ করা", - "removed": "মুছে ফেলা", - "modified": "পরিবর্তিত" - }, - "scatter_chart": { - "title": "{x} বনাম {y} তুলনা করুন", - "freshness_score": "নতুনত্ব স্কোর", - "copy_alt": { - "analysis": "{package}: {x_name} ({x_value}) এবং {y_name} ({y_value})", - "description": "{packages}টি প্যাকেজের জন্য {x_name} বনাম {y_name} এর স্ক্যাটার প্লট চার্ট। {analysis}. {watermark}" - }, - "filename": "{x}-vs-{y}-scatter-chart", - "x_axis": "X-অক্ষ ↦", - "y_axis": "Y-অক্ষ ↥" + } }, "file_changes": "ফাইল পরিবর্তন", "files_count": "{count}টি ফাইল | {count}টি ফাইল", @@ -1436,6 +1432,11 @@ "comparing_versions_label": "সংস্করণ তুলনা করা হচ্ছে...", "version_back_to_package": "প্যাকেজে ফিরে যান", "version_error_message": "সংস্করণ তুলনা ব্যর্থ হয়েছে।", + "version_invalid_url_format": { + "hint": "অবৈধ তুলনা URL। এই ফরম্যাট ব্যবহার করুন: {0}", + "from_version": "থেকে", + "to_version": "পর্যন্ত" + }, "version_selector_title": "সংস্করণের সাথে তুলনা করুন", "summary": "সারাংশ", "deps_count": "{count}টি নির্ভরতা | {count}টি নির্ভরতা", @@ -1444,10 +1445,21 @@ "peer_dependencies": "পিয়ার নির্ভরতা", "optional_dependencies": "ঐচ্ছিক নির্ভরতা", "no_dependency_changes": "কোনো নির্ভরতা পরিবর্তন নেই", + "file_filter_option": { + "all": "সব ({count})", + "added": "যোগ করা হয়েছে ({count})", + "removed": "মুছে ফেলা হয়েছে ({count})", + "modified": "পরিবর্তিত ({count})" + }, "search_files_placeholder": "ফাইল খুঁজুন...", "no_files_all": "কোনো ফাইল নেই", "no_files_search": "\"{query}\" এর সাথে মিল থাকা কোনো ফাইল নেই", "no_files_filtered": "কোনো {filter} ফাইল নেই", + "filter": { + "added": "যোগ করা", + "removed": "মুছে ফেলা", + "modified": "পরিবর্তিত" + }, "files_button": "ফাইলসমূহ", "select_file_prompt": "ডিফ দেখতে সাইডবার থেকে একটি ফাইল নির্বাচন করুন", "close_files_panel": "ফাইল প্যানেল বন্ধ করুন", @@ -1465,6 +1477,8 @@ "word_wrap": "লাইন মোড়ানো" }, "pds": { + "title": "npmx.social", + "meta_description": "npmx কমিউনিটির জন্য অফিসিয়াল AT Protocol Personal Data Server (PDS)।", "join": { "title": "কমিউনিটিতে যোগ দিন", "description": "আপনি অ্যাটমোসফিয়ারে প্রথম অ্যাকাউন্ট তৈরি করছেন বা বিদ্যমান অ্যাকাউন্ট স্থানান্তর করছেন—আপনি এখানে স্বাগতম। আপনি আপনার বর্তমান অ্যাকাউন্ট হ্যান্ডেল, পোস্ট বা ফলোয়ার হারানো ছাড়াই স্থানান্তর করতে পারেন।", @@ -1488,11 +1502,12 @@ "empty": "দেখানোর মতো কোনো কমিউনিটি সদস্য নেই।", "view_profile": "{handle}-এর প্রোফাইল দেখুন", "new_accounts": "...এছাড়া আরও {count}টি নতুন অ্যাকাউন্ট অ্যাটমোসফিয়ারে যুক্ত হয়েছে" - }, - "title": "npmx.social", - "meta_description": "npmx কমিউনিটির জন্য অফিসিয়াল AT Protocol Personal Data Server (PDS)।" + } }, "privacy_policy": { + "title": "প্রাইভেসি পলিসি", + "last_updated": "সর্বশেষ আপডেট: {date}", + "welcome": "{app}-এ স্বাগতম। আমরা আপনার গোপনীয়তা রক্ষায় প্রতিশ্রুতিবদ্ধ। এই নীতিতে আমরা কী ডেটা সংগ্রহ করি, কীভাবে ব্যবহার করি এবং আপনার ডেটা সম্পর্কিত অধিকার ব্যাখ্যা করা হয়েছে।", "cookies": { "what_are": { "title": "কুকি কী?", @@ -1567,12 +1582,12 @@ "changes": { "title": "এই নীতির পরিবর্তন", "p1": "আমরা সময়ে সময়ে এই প্রাইভেসি পলিসি আপডেট করতে পারি। যেকোনো পরিবর্তন এই পাতায় আপডেটেড রিভিশন তারিখসহ প্রকাশ করা হবে।" - }, - "title": "প্রাইভেসি পলিসি", - "last_updated": "সর্বশেষ আপডেট: {date}", - "welcome": "{app}-এ স্বাগতম। আমরা আপনার গোপনীয়তা রক্ষায় প্রতিশ্রুতিবদ্ধ। এই নীতিতে আমরা কী ডেটা সংগ্রহ করি, কীভাবে ব্যবহার করি এবং আপনার ডেটা সম্পর্কিত অধিকার ব্যাখ্যা করা হয়েছে।" + } }, "a11y": { + "title": "অ্যাক্সেসিবিলিটি", + "footer_title": "a11y", + "welcome": "আমরা চাই {app} যত বেশি সম্ভব মানুষের জন্য ব্যবহারযোগ্য হোক।", "approach": { "title": "আমাদের দৃষ্টিভঙ্গি", "p1": "আমরা Web Content Accessibility Guidelines (WCAG) 2.2 অনুসরণ করার চেষ্টা করি এবং ফিচার তৈরি করার সময় এটিকে রেফারেন্স হিসেবে ব্যবহার করি। আমরা কোনো WCAG স্তরের পূর্ণ সম্মতি দাবি করি না—অ্যাক্সেসিবিলিটি একটি চলমান প্রক্রিয়া এবং সবসময় উন্নতির সুযোগ থাকে।", @@ -1597,19 +1612,9 @@ "title": "প্রতিক্রিয়া", "p1": "{app}-এ যদি আপনি কোনো অ্যাক্সেসিবিলিটি সমস্যা পান, অনুগ্রহ করে আমাদের {link}-এ ইস্যু খুলে জানান। আমরা এসব রিপোর্ট গুরুত্ব সহকারে নেই এবং সমাধানের সর্বোচ্চ চেষ্টা করি।", "link": "GitHub রিপোজিটরি" - }, - "title": "অ্যাক্সেসিবিলিটি", - "footer_title": "a11y", - "welcome": "আমরা চাই {app} যত বেশি সম্ভব মানুষের জন্য ব্যবহারযোগ্য হোক।" + } }, "translation_status": { - "table": { - "file": "ফাইল", - "status": "অবস্থা", - "error": "ফাইল তালিকা লোড করার সময় ত্রুটি হয়েছে।", - "empty": "কোনো ফাইল পাওয়া যায়নি", - "file_link": "{file} ({lang}) GitHub-এ সম্পাদনা করুন" - }, "title": "অনুবাদ অবস্থা", "generated_at": "উৎপাদনের তারিখ: {date}", "welcome": "{npmx}-কে বিভিন্ন ভাষায় অনুবাদ করতে সাহায্য করতে আগ্রহী হলে আপনি সঠিক জায়গায় এসেছেন! এই স্বয়ংক্রিয়ভাবে আপডেট হওয়া পেজটি বর্তমানে যেসব কনটেন্টে সাহায্য দরকার তা দেখায়।", @@ -1623,22 +1628,13 @@ "complete_text": "এই অনুবাদ সম্পূর্ণ হয়েছে, দারুণ কাজ!", "missing_text": "অনুপস্থিত", "missing_keys": "কোনো অনুবাদ অনুপস্থিত নেই | অনুপস্থিত অনুবাদ | অনুপস্থিত অনুবাদসমূহ", - "progress_label": "{locale}-এর জন্য অগ্রগতির অবস্থা" - }, - "action_bar": { - "title": "অ্যাকশন বার", - "selection": "০টি নির্বাচিত | ১টি নির্বাচিত | {count}টি নির্বাচিত", - "shortcut": "\"{key}\" চাপুন অ্যাকশন ফোকাস করতে", - "button_close_aria_label": "অ্যাকশন বার বন্ধ করুন" - }, - "leaderboard": { - "likes": { - "title": "লাইক লিডারবোর্ড", - "description": "বর্তমানে npmx-এর সবচেয়ে বেশি লাইক পাওয়া ১০টি প্যাকেজ।", - "rank": "র‍্যাঙ্ক", - "likes": "লাইক", - "unavailable_title": "এখনও কোনো লাইক লিডারবোর্ড নেই", - "unavailable_description": "এই মুহূর্তে দেখানোর মতো কোনো লাইক লিডারবোর্ড নেই।" + "progress_label": "{locale}-এর জন্য অগ্রগতির অবস্থা", + "table": { + "file": "ফাইল", + "status": "অবস্থা", + "error": "ফাইল তালিকা লোড করার সময় ত্রুটি হয়েছে।", + "empty": "কোনো ফাইল পাওয়া যায়নি", + "file_link": "{file} ({lang}) GitHub-এ সম্পাদনা করুন" } }, "vacations": { @@ -1675,6 +1671,12 @@ } } }, + "action_bar": { + "title": "অ্যাকশন বার", + "selection": "০টি নির্বাচিত | ১টি নির্বাচিত | {count}টি নির্বাচিত", + "shortcut": "\"{key}\" চাপুন অ্যাকশন ফোকাস করতে", + "button_close_aria_label": "অ্যাকশন বার বন্ধ করুন" + }, "logo_menu": { "copy_svg": "SVG হিসেবে লোগো কপি করুন", "copied": "কপি হয়েছে!", diff --git a/i18n/locales/pt-PT.json b/i18n/locales/pt-PT.json index 683ce28ae6..54879ed305 100644 --- a/i18n/locales/pt-PT.json +++ b/i18n/locales/pt-PT.json @@ -328,7 +328,6 @@ "cancel": "Cancelar", "save": "Guardar", "edit": "Editar", - "error": "Erro", "view_on": { "npm": "Ver no npm", "github": "Ver no GitHub", @@ -364,6 +363,9 @@ "message": "Parece que ainda não estão a usar o npmx. Queres contar-lhes sobre isso?", "share_button": "Partilhar no Bluesky", "compose_text": "Olá, {'@'}{handle}! Já conheces o npmx.dev? É um navegador para o registo npm rápido, moderno e de código aberto.\nhttps://npmx.dev" + }, + "linked_accounts": { + "status": {} } }, "package": { @@ -591,7 +593,10 @@ "trusted_publisher_added": "Publicação de confiança ativada", "trusted_publisher_removed": "Publicação de confiança removida", "provenance_added": "Proveniência ativada", - "provenance_removed": "Proveniência removida" + "provenance_removed": "Proveniência removida", + "chart": { + "copy_alt": {} + } }, "dependencies": { "title": "Dependência ({count}) | Dependências ({count})", @@ -1704,5 +1709,8 @@ "discord_link_text": "chat.npmx.dev" } }, - "alt_logo_kawaii": "Uma versão fofa, arredondada e colorida do logótipo do npmx." + "alt_logo_kawaii": "Uma versão fofa, arredondada e colorida do logótipo do npmx.", + "changelog": { + "error": {} + } } diff --git a/i18n/locales/ro-RO.json b/i18n/locales/ro-RO.json index 07e880b329..627ddcc17a 100644 --- a/i18n/locales/ro-RO.json +++ b/i18n/locales/ro-RO.json @@ -328,7 +328,6 @@ "cancel": "Anulează", "save": "Salvează", "edit": "Editează", - "error": "Eroare", "view_on": { "npm": "Vizualizează pe npm", "github": "Vizualizează pe GitHub", @@ -364,6 +363,9 @@ "message": "Se pare că încă nu folosesc npmx. Vrei să le spui despre asta?", "share_button": "Partajează pe Bluesky", "compose_text": "Salut {'@'}{handle}! Ai vizitat deja npmx.dev? Este un browser pentru registrul npm, rapid, modern și open-source.\nhttps://npmx.dev" + }, + "linked_accounts": { + "status": {} } }, "package": { From afc5034948886df2125f9a5803a93be8ead7a2a2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 4 Jun 2026 22:30:54 +0530 Subject: [PATCH 26/28] feat: add support for Bluesky platform in account linking and localization --- app/components/AccountItem.vue | 3 ++ app/components/LinkedAccounts.vue | 54 +++++++++++++++++++++++++------ i18n/locales/bn-IN.json | 12 ++++++- i18n/locales/de-AT.json | 7 +++- i18n/locales/de-DE.json | 7 +++- i18n/locales/de.json | 5 +++ i18n/locales/en.json | 5 +++ i18n/locales/pt-PT.json | 7 +++- i18n/locales/ro-RO.json | 7 +++- i18n/schema.json | 15 +++++++++ server/api/keytrace/[domain].ts | 1 + shared/types/keytrace.ts | 2 +- 12 files changed, 110 insertions(+), 15 deletions(-) diff --git a/app/components/AccountItem.vue b/app/components/AccountItem.vue index fa52c82351..a442156bef 100644 --- a/app/components/AccountItem.vue +++ b/app/components/AccountItem.vue @@ -17,6 +17,7 @@ const platformLabelMap: Record = { github: 'GitHub', npm: 'npm', mastodon: 'Mastodon', + bluesky: 'Bluesky', discord: 'Discord', orcid: 'ORCID', } @@ -25,6 +26,7 @@ const platformIconMap: Record = { github: 'i-simple-icons:github', npm: 'i-simple-icons:npm', mastodon: 'i-simple-icons:mastodon', + bluesky: 'i-simple-icons:bluesky', discord: 'i-simple-icons:discord', orcid: 'i-simple-icons:orcid', } @@ -34,6 +36,7 @@ const proofMethodLabelMap: Record = { github: 'GitHub', npm: 'npm', mastodon: 'Mastodon', + bluesky: 'Bluesky', pgp: 'PGP', other: 'other', } diff --git a/app/components/LinkedAccounts.vue b/app/components/LinkedAccounts.vue index 6bc36c035e..b0fd2f86e9 100644 --- a/app/components/LinkedAccounts.vue +++ b/app/components/LinkedAccounts.vue @@ -5,19 +5,19 @@ const { t } = useI18n() const statusLegend = computed(() => [ { - label: t('profile.linked_accounts.status.verified'), + labelKey: 'profile.linked_accounts.status.verified', className: 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30', }, { - label: t('profile.linked_accounts.status.unverified'), + labelKey: 'profile.linked_accounts.status.unverified', className: 'bg-yellow-500/15 text-yellow-300 border-yellow-500/30', }, { - label: t('profile.linked_accounts.status.stale'), + labelKey: 'profile.linked_accounts.status.stale', className: 'bg-orange-500/15 text-orange-300 border-orange-500/30', }, { - label: t('profile.linked_accounts.status.failed'), + labelKey: 'profile.linked_accounts.status.failed', className: 'bg-red-500/15 text-red-300 border-red-500/30', }, ]) @@ -36,9 +36,45 @@ const verifiedCount = computed(