docus 5.5.1 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/app/app.vue +2 -5
  2. package/app/components/app/AppHeaderLogo.vue +12 -7
  3. package/app/composables/useLogoAssets.ts +246 -0
  4. package/app/error.vue +2 -5
  5. package/app/types/index.d.ts +8 -0
  6. package/app/utils/navigation.ts +19 -0
  7. package/content.config.ts +28 -16
  8. package/i18n/locales/ar.json +14 -1
  9. package/i18n/locales/be.json +13 -1
  10. package/i18n/locales/bg.json +14 -1
  11. package/i18n/locales/bn.json +14 -1
  12. package/i18n/locales/ca.json +14 -1
  13. package/i18n/locales/ckb.json +14 -1
  14. package/i18n/locales/cs.json +14 -1
  15. package/i18n/locales/da.json +14 -1
  16. package/i18n/locales/de.json +13 -0
  17. package/i18n/locales/el.json +14 -1
  18. package/i18n/locales/en.json +13 -0
  19. package/i18n/locales/es.json +13 -0
  20. package/i18n/locales/et.json +14 -1
  21. package/i18n/locales/fi.json +14 -1
  22. package/i18n/locales/fr.json +13 -0
  23. package/i18n/locales/he.json +14 -1
  24. package/i18n/locales/hi.json +14 -1
  25. package/i18n/locales/hy.json +14 -1
  26. package/i18n/locales/id.json +36 -23
  27. package/i18n/locales/it.json +13 -0
  28. package/i18n/locales/ja.json +14 -1
  29. package/i18n/locales/kk.json +14 -1
  30. package/i18n/locales/km.json +14 -1
  31. package/i18n/locales/ko.json +14 -1
  32. package/i18n/locales/ky.json +14 -1
  33. package/i18n/locales/lb.json +14 -1
  34. package/i18n/locales/ms.json +14 -1
  35. package/i18n/locales/nb.json +14 -1
  36. package/i18n/locales/nl.json +15 -2
  37. package/i18n/locales/pl.json +13 -1
  38. package/i18n/locales/pt-BR.json +14 -1
  39. package/i18n/locales/ro.json +13 -0
  40. package/i18n/locales/ru.json +13 -1
  41. package/i18n/locales/si.json +14 -1
  42. package/i18n/locales/sl.json +14 -1
  43. package/i18n/locales/sv.json +14 -1
  44. package/i18n/locales/tr.json +34 -21
  45. package/i18n/locales/uk.json +14 -1
  46. package/i18n/locales/ur.json +14 -1
  47. package/i18n/locales/vi.json +14 -1
  48. package/i18n/locales/zh-CN.json +13 -0
  49. package/modules/assistant/runtime/components/AssistantFloatingInput.vue +4 -4
  50. package/modules/routing.ts +21 -17
  51. package/nuxt.config.ts +9 -6
  52. package/nuxt.schema.ts +49 -0
  53. package/package.json +9 -9
  54. package/utils/pages.ts +22 -0
package/app/app.vue CHANGED
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
3
3
  import * as nuxtUiLocales from '@nuxt/ui/locale'
4
+ import { transformNavigation } from './utils/navigation'
4
5
 
5
6
  const { seo } = useAppConfig()
6
7
  const site = useSiteConfig()
@@ -45,11 +46,7 @@ if (isEnabled.value) {
45
46
  }
46
47
 
47
48
  const { data: navigation } = await useAsyncData(() => `navigation_${collectionName.value}`, () => queryCollectionNavigation(collectionName.value as keyof PageCollections), {
48
- transform: (data: ContentNavigationItem[]) => {
49
- const rootResult = data.find(item => item.path === '/docs')?.children || data || []
50
-
51
- return rootResult.find((item: ContentNavigationItem) => item.path === `/${locale.value}`)?.children || rootResult
52
- },
49
+ transform: (data: ContentNavigationItem[]) => transformNavigation(data, isEnabled.value, locale.value),
53
50
  watch: [locale],
54
51
  })
55
52
  const { data: files } = useLazyAsyncData(`search_${collectionName.value}`, () => queryCollectionSearchSections(collectionName.value as keyof PageCollections), {
@@ -1,15 +1,20 @@
1
1
  <script setup lang="ts">
2
2
  const appConfig = useAppConfig()
3
+ const { hasLogo, headerLightUrl, headerDarkUrl, contextMenuItems } = useLogoAssets()
3
4
  </script>
4
5
 
5
6
  <template>
6
- <UColorModeImage
7
- v-if="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
8
- :light="appConfig.header?.logo?.light || appConfig.header?.logo?.dark"
9
- :dark="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
10
- :alt="appConfig.header?.logo?.alt || appConfig.header?.title"
11
- class="h-6 w-auto shrink-0"
12
- />
7
+ <UContextMenu
8
+ v-if="hasLogo"
9
+ :items="contextMenuItems"
10
+ >
11
+ <UColorModeImage
12
+ :light="headerLightUrl"
13
+ :dark="headerDarkUrl"
14
+ :alt="appConfig.header?.logo?.alt || appConfig.header?.title"
15
+ :class="['h-6 w-auto shrink-0', appConfig.header?.logo?.class]"
16
+ />
17
+ </UContextMenu>
13
18
  <span v-else>
14
19
  {{ appConfig.header?.title || '{appConfig.header.title}' }}
15
20
  </span>
@@ -0,0 +1,246 @@
1
+ import type { ContextMenuItem } from '@nuxt/ui'
2
+
3
+ function isSvgUrl(url: string): boolean {
4
+ return url.toLowerCase().endsWith('.svg')
5
+ }
6
+
7
+ function getExtension(url: string): string {
8
+ const match = url.match(/\.([a-z0-9]+)(?:\?|$)/i)
9
+ return match?.[1] ? `.${match[1].toLowerCase()}` : '.png'
10
+ }
11
+
12
+ function normalizeSvg(svg: string, name: string): string {
13
+ let result = svg.replace(/fill="(black|white|#[0-9a-fA-F]{3,8}|rgba?\([^)]+\))"/g, 'fill="currentColor"')
14
+
15
+ if (name) {
16
+ result = result.replace(/<svg\b/, `<svg id="${name}"`)
17
+ result = result.replace(/(<svg[^>]*>)/, `$1<title>${name}</title>`)
18
+ }
19
+
20
+ return result
21
+ }
22
+
23
+ async function fetchSvgContent(url: string, name: string): Promise<string | null> {
24
+ try {
25
+ const absoluteUrl = new URL(url, window.location.origin).href
26
+ const response = await fetch(absoluteUrl)
27
+ if (!response.ok) return null
28
+ const text = await response.text()
29
+ return normalizeSvg(text, name)
30
+ }
31
+ catch {
32
+ return null
33
+ }
34
+ }
35
+
36
+ async function copyTextToClipboard(text: string): Promise<boolean> {
37
+ try {
38
+ await navigator.clipboard.writeText(text)
39
+ return true
40
+ }
41
+ catch {
42
+ return false
43
+ }
44
+ }
45
+
46
+ function triggerDownload(blob: Blob, filename: string) {
47
+ const url = URL.createObjectURL(blob)
48
+ const link = document.createElement('a')
49
+ link.href = url
50
+ link.download = filename
51
+ document.body.appendChild(link)
52
+ link.click()
53
+ document.body.removeChild(link)
54
+ URL.revokeObjectURL(url)
55
+ }
56
+
57
+ function triggerLinkDownload(url: string, filename: string) {
58
+ const link = document.createElement('a')
59
+ link.href = url
60
+ link.download = filename
61
+ document.body.appendChild(link)
62
+ link.click()
63
+ document.body.removeChild(link)
64
+ }
65
+
66
+ export const useLogoAssets = () => {
67
+ const appConfig = useAppConfig()
68
+ const colorMode = useColorMode()
69
+ const toast = useToast()
70
+ const { t } = useDocusI18n()
71
+
72
+ const hasLogo = computed(() => !!(appConfig.header?.logo?.light || appConfig.header?.logo?.dark))
73
+
74
+ const displayMode = computed(() => appConfig.header?.logo?.display || 'logo')
75
+
76
+ const currentLogoUrl = computed(() => {
77
+ const logo = appConfig.header?.logo
78
+ if (!logo) return ''
79
+ if (colorMode.value === 'dark') return logo.dark || logo.light || ''
80
+ return logo.light || logo.dark || ''
81
+ })
82
+
83
+ const hasWordmark = computed(() => {
84
+ const wm = appConfig.header?.logo?.wordmark
85
+ return !!(wm?.light || wm?.dark)
86
+ })
87
+
88
+ const currentWordmarkUrl = computed(() => {
89
+ const wm = appConfig.header?.logo?.wordmark
90
+ if (!wm) return ''
91
+ if (colorMode.value === 'dark') return wm.dark || wm.light || ''
92
+ return wm.light || wm.dark || ''
93
+ })
94
+
95
+ const headerLightUrl = computed(() => {
96
+ const logo = appConfig.header?.logo
97
+ if (!logo) return ''
98
+ if (displayMode.value === 'wordmark' && hasWordmark.value) {
99
+ return logo.wordmark?.light || logo.wordmark?.dark || logo.light || logo.dark || ''
100
+ }
101
+ return logo.light || logo.dark || ''
102
+ })
103
+
104
+ const headerDarkUrl = computed(() => {
105
+ const logo = appConfig.header?.logo
106
+ if (!logo) return ''
107
+ if (displayMode.value === 'wordmark' && hasWordmark.value) {
108
+ return logo.wordmark?.dark || logo.wordmark?.light || logo.dark || logo.light || ''
109
+ }
110
+ return logo.dark || logo.light || ''
111
+ })
112
+
113
+ const faviconUrl = computed(() => appConfig.header?.logo?.favicon || '/favicon.ico')
114
+
115
+ const logoAlt = computed(() => appConfig.header?.logo?.alt || appConfig.header?.title || '')
116
+
117
+ const brandName = computed(() => appConfig.header?.title || logoAlt.value || '')
118
+
119
+ const prefix = computed(() => {
120
+ const name = brandName.value
121
+ return name ? name.toLowerCase().replace(/\s+/g, '-') : 'logo'
122
+ })
123
+
124
+ const logoName = computed(() => {
125
+ const name = brandName.value
126
+ return name ? `${name} Logo` : 'Logo'
127
+ })
128
+
129
+ const wordmarkName = computed(() => {
130
+ const name = brandName.value
131
+ return name ? `${name} Wordmark` : 'Wordmark'
132
+ })
133
+
134
+ const logoIsSvg = computed(() => isSvgUrl(currentLogoUrl.value))
135
+ const wordmarkIsSvg = computed(() => isSvgUrl(currentWordmarkUrl.value))
136
+
137
+ async function copyLogo() {
138
+ if (!logoIsSvg.value) return
139
+ const svg = await fetchSvgContent(currentLogoUrl.value, logoName.value)
140
+ if (!svg) {
141
+ toast.add({ title: t('logo.copyLogoFailed'), icon: 'i-lucide-circle-x', color: 'error' })
142
+ return
143
+ }
144
+ const ok = await copyTextToClipboard(svg)
145
+ toast.add(ok
146
+ ? { title: t('logo.logoCopied'), icon: 'i-lucide-circle-check', color: 'success' }
147
+ : { title: t('logo.copyLogoFailed'), icon: 'i-lucide-circle-x', color: 'error' },
148
+ )
149
+ }
150
+
151
+ async function copyWordmark() {
152
+ if (!wordmarkIsSvg.value) return
153
+ const svg = await fetchSvgContent(currentWordmarkUrl.value, wordmarkName.value)
154
+ if (!svg) {
155
+ toast.add({ title: t('logo.copyWordmarkFailed'), icon: 'i-lucide-circle-x', color: 'error' })
156
+ return
157
+ }
158
+ const ok = await copyTextToClipboard(svg)
159
+ toast.add(ok
160
+ ? { title: t('logo.wordmarkCopied'), icon: 'i-lucide-circle-check', color: 'success' }
161
+ : { title: t('logo.copyWordmarkFailed'), icon: 'i-lucide-circle-x', color: 'error' },
162
+ )
163
+ }
164
+
165
+ async function downloadLogo() {
166
+ const url = currentLogoUrl.value
167
+ if (logoIsSvg.value) {
168
+ const svg = await fetchSvgContent(url, logoName.value)
169
+ if (!svg) return
170
+ triggerDownload(new Blob([svg], { type: 'image/svg+xml' }), `${prefix.value}-logo.svg`)
171
+ }
172
+ else {
173
+ triggerLinkDownload(url, `${prefix.value}-logo${getExtension(url)}`)
174
+ }
175
+ toast.add({ title: t('logo.logoDownloaded'), icon: 'i-lucide-download', color: 'success' })
176
+ }
177
+
178
+ async function downloadWordmark() {
179
+ const url = currentWordmarkUrl.value
180
+ if (wordmarkIsSvg.value) {
181
+ const svg = await fetchSvgContent(url, wordmarkName.value)
182
+ if (!svg) return
183
+ triggerDownload(new Blob([svg], { type: 'image/svg+xml' }), `${prefix.value}-wordmark.svg`)
184
+ }
185
+ else {
186
+ triggerLinkDownload(url, `${prefix.value}-wordmark${getExtension(url)}`)
187
+ }
188
+ toast.add({ title: t('logo.wordmarkDownloaded'), icon: 'i-lucide-download', color: 'success' })
189
+ }
190
+
191
+ const brandAssetsUrl = computed(() => appConfig.header?.logo?.brandAssetsUrl || '')
192
+
193
+ const contextMenuItems = computed(() => {
194
+ if (!hasLogo.value) return []
195
+
196
+ const copyGroup: ContextMenuItem[] = []
197
+ if (logoIsSvg.value) {
198
+ copyGroup.push({ label: t('logo.copyLogo'), icon: 'i-lucide-copy', onSelect: copyLogo })
199
+ }
200
+ if (hasWordmark.value && wordmarkIsSvg.value) {
201
+ copyGroup.push({ label: t('logo.copyWordmark'), icon: 'i-lucide-copy', onSelect: copyWordmark })
202
+ }
203
+
204
+ const downloadGroup: ContextMenuItem[] = [
205
+ { label: t('logo.downloadLogo'), icon: 'i-lucide-download', onSelect: downloadLogo },
206
+ ]
207
+ if (hasWordmark.value) {
208
+ downloadGroup.push({ label: t('logo.downloadWordmark'), icon: 'i-lucide-download', onSelect: downloadWordmark })
209
+ }
210
+
211
+ const items: ContextMenuItem[][] = []
212
+ if (copyGroup.length) items.push(copyGroup)
213
+ items.push(downloadGroup)
214
+
215
+ if (brandAssetsUrl.value) {
216
+ items.push([{
217
+ label: t('logo.brandAssets'),
218
+ icon: 'i-lucide-palette',
219
+ onSelect() {
220
+ window.open(brandAssetsUrl.value, '_blank')
221
+ },
222
+ }])
223
+ }
224
+
225
+ return items
226
+ })
227
+
228
+ return {
229
+ hasLogo,
230
+ displayMode,
231
+ currentLogoUrl,
232
+ headerLightUrl,
233
+ headerDarkUrl,
234
+ hasWordmark,
235
+ currentWordmarkUrl,
236
+ faviconUrl,
237
+ logoAlt,
238
+ contextMenuItems,
239
+ copyLogo,
240
+ downloadLogo,
241
+ copyWordmark,
242
+ downloadWordmark,
243
+ copyTextToClipboard,
244
+ fetchSvgContent,
245
+ }
246
+ }
package/app/error.vue CHANGED
@@ -2,6 +2,7 @@
2
2
  import type { NuxtError } from '#app'
3
3
  import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
4
4
  import * as nuxtUiLocales from '@nuxt/ui/locale'
5
+ import { transformNavigation } from './utils/navigation'
5
6
 
6
7
  const props = defineProps<{
7
8
  error: NuxtError
@@ -47,11 +48,7 @@ if (isEnabled.value) {
47
48
  const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
48
49
 
49
50
  const { data: navigation } = await useAsyncData(`navigation_${collectionName.value}`, () => queryCollectionNavigation(collectionName.value as keyof PageCollections), {
50
- transform: (data: ContentNavigationItem[]) => {
51
- const rootResult = data.find(item => item.path === '/docs')?.children || data || []
52
-
53
- return rootResult.find(item => item.path === `/${locale.value}`)?.children || rootResult
54
- },
51
+ transform: (data: ContentNavigationItem[]) => transformNavigation(data, isEnabled.value, locale.value),
55
52
  watch: [locale],
56
53
  })
57
54
  const { data: files } = useLazyAsyncData(`search_${collectionName.value}`, () => queryCollectionSearchSections(collectionName.value as keyof PageCollections), {
@@ -18,6 +18,14 @@ declare module 'nuxt/schema' {
18
18
  light: string
19
19
  dark: string
20
20
  alt: string
21
+ class?: string
22
+ display?: 'logo' | 'wordmark'
23
+ wordmark?: {
24
+ light?: string
25
+ dark?: string
26
+ }
27
+ favicon?: string
28
+ brandAssetsUrl?: string
21
29
  }
22
30
  }
23
31
  socials: Record<string, string>
@@ -6,6 +6,25 @@ export const flattenNavigation = (items?: ContentNavigationItem[]): ContentNavig
6
6
  : [item],
7
7
  ) || []
8
8
 
9
+ /**
10
+ * Transform navigation data by stripping locale and docs levels
11
+ */
12
+ export function transformNavigation(
13
+ data: ContentNavigationItem[],
14
+ isI18nEnabled: boolean,
15
+ locale?: string,
16
+ ): ContentNavigationItem[] {
17
+ if (isI18nEnabled && locale) {
18
+ // i18n: first strip locale level, then check for docs level
19
+ const localeResult = data.find(item => item.path === `/${locale}`)?.children || data
20
+ return localeResult.find(item => item.path === `/${locale}/docs`)?.children || localeResult
21
+ }
22
+ else {
23
+ // non-i18n: strip docs level if exists
24
+ return data.find(item => item.path === '/docs')?.children || data
25
+ }
26
+ }
27
+
9
28
  export interface BreadcrumbItem {
10
29
  title: string
11
30
  path: string
package/content.config.ts CHANGED
@@ -2,11 +2,15 @@ import type { DefinedCollection } from '@nuxt/content'
2
2
  import { defineContentConfig, defineCollection, z } from '@nuxt/content'
3
3
  import { useNuxt } from '@nuxt/kit'
4
4
  import { joinURL } from 'ufo'
5
+ import { landingPageExists, docsFolderExists } from './utils/pages'
5
6
 
6
7
  const { options } = useNuxt()
7
8
  const cwd = joinURL(options.rootDir, 'content')
8
9
  const locales = options.i18n?.locales
9
10
 
11
+ const hasLandingPage = landingPageExists(options.rootDir)
12
+ const hasDocsFolder = docsFolderExists(options.rootDir)
13
+
10
14
  const createDocsSchema = () => z.object({
11
15
  links: z.array(z.object({
12
16
  label: z.string(),
@@ -22,21 +26,24 @@ if (locales && Array.isArray(locales)) {
22
26
  collections = {}
23
27
  for (const locale of locales) {
24
28
  const code = (typeof locale === 'string' ? locale : locale.code).replace('-', '_')
29
+ const hasLocaleDocs = docsFolderExists(options.rootDir, code)
25
30
 
26
- collections[`landing_${code}`] = defineCollection({
27
- type: 'page',
28
- source: {
29
- cwd,
30
- include: `${code}/index.md`,
31
- },
32
- })
31
+ if (!hasLandingPage) {
32
+ collections[`landing_${code}`] = defineCollection({
33
+ type: 'page',
34
+ source: {
35
+ cwd,
36
+ include: `${code}/index.md`,
37
+ },
38
+ })
39
+ }
33
40
 
34
41
  collections[`docs_${code}`] = defineCollection({
35
42
  type: 'page',
36
43
  source: {
37
44
  cwd,
38
- include: `${code}/**/*`,
39
- prefix: `/${code}`,
45
+ include: hasLocaleDocs ? `${code}/docs/**` : `${code}/**/*`,
46
+ prefix: hasLocaleDocs ? `/${code}/docs` : `/${code}`,
40
47
  exclude: [`${code}/index.md`],
41
48
  },
42
49
  schema: createDocsSchema(),
@@ -45,22 +52,27 @@ if (locales && Array.isArray(locales)) {
45
52
  }
46
53
  else {
47
54
  collections = {
48
- landing: defineCollection({
55
+ docs: defineCollection({
49
56
  type: 'page',
50
57
  source: {
51
58
  cwd,
52
- include: 'index.md',
59
+ include: hasDocsFolder ? 'docs/**' : '**',
60
+ prefix: hasDocsFolder ? '/docs' : '/',
61
+ exclude: ['index.md'],
53
62
  },
63
+ schema: createDocsSchema(),
54
64
  }),
55
- docs: defineCollection({
65
+ }
66
+
67
+ // Only define landing collection if user doesn't have their own index.vue
68
+ if (!hasLandingPage) {
69
+ collections.landing = defineCollection({
56
70
  type: 'page',
57
71
  source: {
58
72
  cwd,
59
- include: '**',
60
- exclude: ['index.md'],
73
+ include: 'index.md',
61
74
  },
62
- schema: createDocsSchema(),
63
- }),
75
+ })
64
76
  }
65
77
  }
66
78
 
@@ -18,5 +18,18 @@
18
18
  "toc": "في هذه الصفحة",
19
19
  "report": "الإبلاغ عن مشكلة",
20
20
  "edit": "تحرير هذه الصفحة"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "نسخ الشعار",
24
+ "copyWordmark": "نسخ العلامة النصية",
25
+ "downloadLogo": "تحميل الشعار",
26
+ "downloadWordmark": "تحميل العلامة النصية",
27
+ "brandAssets": "أصول العلامة التجارية",
28
+ "logoCopied": "تم نسخ الشعار",
29
+ "wordmarkCopied": "تم نسخ العلامة النصية",
30
+ "logoDownloaded": "تم تحميل الشعار",
31
+ "wordmarkDownloaded": "تم تحميل العلامة النصية",
32
+ "copyLogoFailed": "فشل نسخ الشعار",
33
+ "copyWordmarkFailed": "فشل نسخ العلامة النصية"
21
34
  }
22
- }
35
+ }
@@ -18,6 +18,18 @@
18
18
  "toc": "На гэтай старонцы",
19
19
  "report": "Паведаміць пра праблему",
20
20
  "edit": "Рэдагаваць гэтую старонку"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "Капіяваць лагатып",
24
+ "copyWordmark": "Капіяваць словесны знак",
25
+ "downloadLogo": "Спампаваць лагатып",
26
+ "downloadWordmark": "Спампаваць словесны знак",
27
+ "brandAssets": "Матэрыялы брэнда",
28
+ "logoCopied": "Лагатып скапіяваны",
29
+ "wordmarkCopied": "Словесны знак скапіяваны",
30
+ "logoDownloaded": "Лагатып спампаваны",
31
+ "wordmarkDownloaded": "Словесны знак спампаваны",
32
+ "copyLogoFailed": "Не ўдалося скапіяваць лагатып",
33
+ "copyWordmarkFailed": "Не ўдалося скапіяваць словесны знак"
21
34
  }
22
35
  }
23
-
@@ -18,5 +18,18 @@
18
18
  "toc": "На тази страница",
19
19
  "report": "Докладване на проблем",
20
20
  "edit": "Редактиране на тази страница"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "Копиране на логото",
24
+ "copyWordmark": "Копиране на словната марка",
25
+ "downloadLogo": "Изтегляне на логото",
26
+ "downloadWordmark": "Изтегляне на словната марка",
27
+ "brandAssets": "Брандови материали",
28
+ "logoCopied": "Логото е копирано",
29
+ "wordmarkCopied": "Словната марка е копирана",
30
+ "logoDownloaded": "Логото е изтеглено",
31
+ "wordmarkDownloaded": "Словната марка е изтеглена",
32
+ "copyLogoFailed": "Неуспешно копиране на логото",
33
+ "copyWordmarkFailed": "Неуспешно копиране на словната марка"
21
34
  }
22
- }
35
+ }
@@ -18,5 +18,18 @@
18
18
  "toc": "এই পেজে",
19
19
  "report": "সমস্যা রিপোর্ট করুন",
20
20
  "edit": "এই পেজ সম্পাদনা করুন"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "লোগো কপি করুন",
24
+ "copyWordmark": "ওয়ার্ডমার্ক কপি করুন",
25
+ "downloadLogo": "লোগো ডাউনলোড করুন",
26
+ "downloadWordmark": "ওয়ার্ডমার্ক ডাউনলোড করুন",
27
+ "brandAssets": "ব্র্যান্ড অ্যাসেট",
28
+ "logoCopied": "লোগো কপি হয়েছে",
29
+ "wordmarkCopied": "ওয়ার্ডমার্ক কপি হয়েছে",
30
+ "logoDownloaded": "লোগো ডাউনলোড হয়েছে",
31
+ "wordmarkDownloaded": "ওয়ার্ডমার্ক ডাউনলোড হয়েছে",
32
+ "copyLogoFailed": "লোগো কপি করা যায়নি",
33
+ "copyWordmarkFailed": "ওয়ার্ডমার্ক কপি করা যায়নি"
21
34
  }
22
- }
35
+ }
@@ -18,5 +18,18 @@
18
18
  "toc": "En aquesta pàgina",
19
19
  "report": "Informar d'un problema",
20
20
  "edit": "Editar aquesta pàgina"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "Copiar el logotip",
24
+ "copyWordmark": "Copiar el wordmark",
25
+ "downloadLogo": "Descarregar el logotip",
26
+ "downloadWordmark": "Descarregar el wordmark",
27
+ "brandAssets": "Recursos de marca",
28
+ "logoCopied": "Logotip copiat",
29
+ "wordmarkCopied": "Wordmark copiat",
30
+ "logoDownloaded": "Logotip descarregat",
31
+ "wordmarkDownloaded": "Wordmark descarregat",
32
+ "copyLogoFailed": "No s'ha pogut copiar el logotip",
33
+ "copyWordmarkFailed": "No s'ha pogut copiar el wordmark"
21
34
  }
22
- }
35
+ }
@@ -14,5 +14,18 @@
14
14
  "toc": "لەم پەڕەدا",
15
15
  "report": "ڕاپۆرتکردنی کێشە",
16
16
  "edit": "دەستکاریکردنی ئەم پەڕەیە"
17
+ },
18
+ "logo": {
19
+ "copyLogo": "کۆپیکردنی لۆگۆ",
20
+ "copyWordmark": "کۆپیکردنی وشەنیشان",
21
+ "downloadLogo": "داگرتنی لۆگۆ",
22
+ "downloadWordmark": "داگرتنی وشەنیشان",
23
+ "brandAssets": "سامانەکانی براند",
24
+ "logoCopied": "لۆگۆ کۆپی کرا",
25
+ "wordmarkCopied": "وشەنیشان کۆپی کرا",
26
+ "logoDownloaded": "لۆگۆ دابەزێنرا",
27
+ "wordmarkDownloaded": "وشەنیشان دابەزێنرا",
28
+ "copyLogoFailed": "کۆپیکردنی لۆگۆ سەرکەوتوو نەبوو",
29
+ "copyWordmarkFailed": "کۆپیکردنی وشەنیشان سەرکەوتوو نەبوو"
17
30
  }
18
- }
31
+ }
@@ -18,5 +18,18 @@
18
18
  "toc": "Na této stránce",
19
19
  "report": "Nahlásit problém",
20
20
  "edit": "Upravit tuto stránku"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "Kopírovat logo",
24
+ "copyWordmark": "Kopírovat wordmark",
25
+ "downloadLogo": "Stáhnout logo",
26
+ "downloadWordmark": "Stáhnout wordmark",
27
+ "brandAssets": "Materiály značky",
28
+ "logoCopied": "Logo zkopírováno",
29
+ "wordmarkCopied": "Wordmark zkopírován",
30
+ "logoDownloaded": "Logo staženo",
31
+ "wordmarkDownloaded": "Wordmark stažen",
32
+ "copyLogoFailed": "Kopírování loga selhalo",
33
+ "copyWordmarkFailed": "Kopírování wordmarku selhalo"
21
34
  }
22
- }
35
+ }
@@ -18,5 +18,18 @@
18
18
  "toc": "På denne side",
19
19
  "report": "Rapporter et problem",
20
20
  "edit": "Rediger denne side"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "Kopiér logo",
24
+ "copyWordmark": "Kopiér wordmark",
25
+ "downloadLogo": "Download logo",
26
+ "downloadWordmark": "Download wordmark",
27
+ "brandAssets": "Brandmaterialer",
28
+ "logoCopied": "Logo kopieret",
29
+ "wordmarkCopied": "Wordmark kopieret",
30
+ "logoDownloaded": "Logo downloadet",
31
+ "wordmarkDownloaded": "Wordmark downloadet",
32
+ "copyLogoFailed": "Kunne ikke kopiere logo",
33
+ "copyWordmarkFailed": "Kunne ikke kopiere wordmark"
21
34
  }
22
- }
35
+ }
@@ -18,5 +18,18 @@
18
18
  "toc": "Auf dieser Seite",
19
19
  "report": "Problem melden",
20
20
  "edit": "Diese Seite bearbeiten"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "Logo kopieren",
24
+ "copyWordmark": "Wortmarke kopieren",
25
+ "downloadLogo": "Logo herunterladen",
26
+ "downloadWordmark": "Wortmarke herunterladen",
27
+ "brandAssets": "Markenmaterialien",
28
+ "logoCopied": "Logo kopiert",
29
+ "wordmarkCopied": "Wortmarke kopiert",
30
+ "logoDownloaded": "Logo heruntergeladen",
31
+ "wordmarkDownloaded": "Wortmarke heruntergeladen",
32
+ "copyLogoFailed": "Logo konnte nicht kopiert werden",
33
+ "copyWordmarkFailed": "Wortmarke konnte nicht kopiert werden"
21
34
  }
22
35
  }
@@ -18,5 +18,18 @@
18
18
  "toc": "Σε αυτή τη σελίδα",
19
19
  "report": "Αναφορά προβλήματος",
20
20
  "edit": "Επεξεργασία αυτής της σελίδας"
21
+ },
22
+ "logo": {
23
+ "copyLogo": "Αντιγραφή λογοτύπου",
24
+ "copyWordmark": "Αντιγραφή wordmark",
25
+ "downloadLogo": "Λήψη λογοτύπου",
26
+ "downloadWordmark": "Λήψη wordmark",
27
+ "brandAssets": "Υλικά επωνυμίας",
28
+ "logoCopied": "Το λογότυπο αντιγράφηκε",
29
+ "wordmarkCopied": "Το wordmark αντιγράφηκε",
30
+ "logoDownloaded": "Το λογότυπο κατέβηκε",
31
+ "wordmarkDownloaded": "Το wordmark κατέβηκε",
32
+ "copyLogoFailed": "Αποτυχία αντιγραφής λογοτύπου",
33
+ "copyWordmarkFailed": "Αποτυχία αντιγραφής wordmark"
21
34
  }
22
- }
35
+ }
@@ -19,6 +19,19 @@
19
19
  "report": "Report an issue",
20
20
  "edit": "Edit this page"
21
21
  },
22
+ "logo": {
23
+ "copyLogo": "Copy logo",
24
+ "copyWordmark": "Copy wordmark",
25
+ "downloadLogo": "Download logo",
26
+ "downloadWordmark": "Download wordmark",
27
+ "brandAssets": "Brand assets",
28
+ "logoCopied": "Logo copied",
29
+ "wordmarkCopied": "Wordmark copied",
30
+ "logoDownloaded": "Logo downloaded",
31
+ "wordmarkDownloaded": "Wordmark downloaded",
32
+ "copyLogoFailed": "Failed to copy logo",
33
+ "copyWordmarkFailed": "Failed to copy wordmark"
34
+ },
22
35
  "assistant": {
23
36
  "title": "Ask AI",
24
37
  "placeholder": "Ask a question...",