[READ-ONLY] a fast, modern browser for the npm registry
at main 141 lines 4.7 kB view raw
1<script setup lang="ts"> 2import type { I18nLocaleStatus } from '#shared/types' 3 4const props = defineProps<{ 5 status: I18nLocaleStatus 6}>() 7 8// Show first N missing keys by default 9const INITIAL_SHOW_COUNT = 5 10const showAll = shallowRef(false) 11 12const missingKeysToShow = computed(() => { 13 if (showAll.value || props.status.missingKeys.length <= INITIAL_SHOW_COUNT) { 14 return props.status.missingKeys 15 } 16 return props.status.missingKeys.slice(0, INITIAL_SHOW_COUNT) 17}) 18 19const hasMoreKeys = computed( 20 () => props.status.missingKeys.length > INITIAL_SHOW_COUNT && !showAll.value, 21) 22 23const remainingCount = computed(() => props.status.missingKeys.length - INITIAL_SHOW_COUNT) 24 25// Generate a GitHub URL that pre-fills the edit with guidance 26const contributionGuideUrl = 27 'https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#localization-i18n' 28 29// Copy missing keys as JSON template to clipboard 30const { copy, copied } = useClipboard() 31 32const numberFormatter = useNumberFormatter() 33const percentageFormatter = useNumberFormatter({ style: 'percent' }) 34 35function copyMissingKeysTemplate() { 36 // Create a template showing what needs to be added 37 const template = props.status.missingKeys.map(key => ` "${key}": ""`).join(',\n') 38 39 const fullTemplate = `// Missing translations for ${props.status.label} (${props.status.lang}) 40// Add these keys to: i18n/locales/${props.status.lang}.json 41 42${template}` 43 44 copy(fullTemplate) 45} 46</script> 47 48<template> 49 <div class="space-y-3"> 50 <!-- Progress section --> 51 <div class="space-y-1.5"> 52 <div class="flex items-center justify-between text-xs text-fg-muted"> 53 <span>{{ $t('settings.translation_progress') }}</span> 54 <span class="tabular-nums" 55 >{{ numberFormatter.format(status.completedKeys) }}/{{ 56 numberFormatter.format(status.totalKeys) 57 }} 58 ({{ percentageFormatter.format(status.percentComplete / 100) }})</span 59 > 60 </div> 61 <div class="h-1.5 bg-bg rounded-full overflow-hidden"> 62 <div 63 class="h-full bg-accent transition-all duration-300 motion-reduce:transition-none" 64 :style="{ width: `${status.percentComplete}%` }" 65 /> 66 </div> 67 </div> 68 69 <!-- Missing keys section --> 70 <div v-if="status.missingKeys.length > 0" class="space-y-2"> 71 <div class="flex items-center justify-between"> 72 <h4 class="text-xs text-fg-muted font-medium"> 73 {{ 74 $t( 75 'i18n.missing_keys', 76 { count: numberFormatter.format(status.missingKeys.length) }, 77 status.missingKeys.length, 78 ) 79 }} 80 </h4> 81 <button 82 type="button" 83 class="text-xs text-accent hover:underline rounded focus-visible:outline-accent/70" 84 @click="copyMissingKeysTemplate" 85 > 86 {{ copied ? $t('common.copied') : $t('i18n.copy_keys') }} 87 </button> 88 </div> 89 90 <ul class="space-y-1 text-xs font-mono bg-bg rounded-md p-2 max-h-32 overflow-y-auto"> 91 <li v-for="key in missingKeysToShow" :key="key" class="text-fg-muted truncate" :title="key"> 92 {{ key }} 93 </li> 94 </ul> 95 96 <button 97 v-if="hasMoreKeys" 98 type="button" 99 class="text-xs text-fg-muted hover:text-fg rounded focus-visible:outline-accent/70" 100 @click="showAll = true" 101 > 102 {{ 103 $t( 104 'i18n.show_more_keys', 105 { count: numberFormatter.format(remainingCount) }, 106 remainingCount, 107 ) 108 }} 109 </button> 110 </div> 111 112 <!-- Contribution guidance --> 113 <div class="pt-2 border-t border-border space-y-2"> 114 <p class="text-xs text-fg-muted"> 115 {{ $t('i18n.contribute_hint') }} 116 </p> 117 118 <div class="flex flex-wrap gap-2"> 119 <a 120 :href="status.githubEditUrl" 121 target="_blank" 122 rel="noopener noreferrer" 123 class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs bg-bg hover:bg-bg-subtle border border-border rounded-md transition-colors focus-visible:outline-accent/70" 124 > 125 <span class="i-lucide:pen w-3.5 h-3.5" aria-hidden="true" /> 126 {{ $t('i18n.edit_on_github') }} 127 </a> 128 129 <a 130 :href="contributionGuideUrl" 131 target="_blank" 132 rel="noopener noreferrer" 133 class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs text-fg-muted hover:text-fg rounded transition-colors focus-visible:outline-accent/70" 134 > 135 <span class="i-lucide:file-text w-3.5 h-3.5" aria-hidden="true" /> 136 {{ $t('i18n.view_guide') }} 137 </a> 138 </div> 139 </div> 140 </div> 141</template>