@uxfront/layer-docs 0.1.0 → 0.2.0

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 (53) hide show
  1. package/CHANGELOG.md +57 -5
  2. package/README.md +18 -0
  3. package/app/app.config.ts +14 -0
  4. package/app/components/IconMenuToggle.vue +14 -1
  5. package/app/components/app/AppHeader.vue +20 -2
  6. package/app/components/app/AppHeaderAttribution.vue +45 -0
  7. package/app/components/app/AppSubHeader.vue +2 -2
  8. package/app/components/content/FrameworkSwitcher.vue +1 -0
  9. package/app/components/content/GradientPageHero.vue +35 -0
  10. package/app/components/docs/DocsAsideLeftTop.vue +24 -0
  11. package/app/components/docs/DocsFrameworkSelect.vue +18 -0
  12. package/app/components/docs/DocsPageHeaderLinks.vue +1 -0
  13. package/app/composables/useDocsSections.ts +8 -1
  14. package/app/composables/useFramework.ts +20 -29
  15. package/app/layouts/default.vue +1 -0
  16. package/app/pages/[[lang]]/docs/[section]/[...slug].vue +4 -1
  17. package/app/plugins/posthog.client.ts +6 -0
  18. package/i18n/locales/ar.json +24 -0
  19. package/i18n/locales/be.json +24 -0
  20. package/i18n/locales/bn.json +24 -0
  21. package/i18n/locales/ca.json +24 -0
  22. package/i18n/locales/ckb.json +24 -0
  23. package/i18n/locales/cs.json +24 -0
  24. package/i18n/locales/da.json +24 -0
  25. package/i18n/locales/de.json +24 -0
  26. package/i18n/locales/el.json +24 -0
  27. package/i18n/locales/en.json +2 -0
  28. package/i18n/locales/et.json +24 -0
  29. package/i18n/locales/fr.json +24 -0
  30. package/i18n/locales/he.json +24 -0
  31. package/i18n/locales/hi.json +24 -0
  32. package/i18n/locales/hy.json +24 -0
  33. package/i18n/locales/it.json +24 -0
  34. package/i18n/locales/ja.json +24 -0
  35. package/i18n/locales/kk.json +24 -0
  36. package/i18n/locales/km.json +24 -0
  37. package/i18n/locales/ko.json +24 -0
  38. package/i18n/locales/ky.json +24 -0
  39. package/i18n/locales/lb.json +24 -0
  40. package/i18n/locales/ms.json +24 -0
  41. package/i18n/locales/nb.json +24 -0
  42. package/i18n/locales/pl.json +24 -0
  43. package/i18n/locales/ru.json +24 -0
  44. package/i18n/locales/sl.json +24 -0
  45. package/i18n/locales/sv.json +24 -0
  46. package/i18n/locales/uk.json +24 -0
  47. package/i18n/locales/ur.json +24 -0
  48. package/i18n/locales/vi.json +24 -0
  49. package/modules/optimizeDeps.ts +28 -0
  50. package/nuxt.config.ts +1 -0
  51. package/nuxt.schema.ts +359 -0
  52. package/package.json +2 -1
  53. package/utils/content.ts +85 -10
package/CHANGELOG.md CHANGED
@@ -4,11 +4,50 @@
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Fix `nuxt typecheck` failure in `AppHeaderCTA.vue`: the config module seeds a
8
- partial `header` (`{ title }`), so defu narrows the resolved `AppConfig` header
9
- to `{ title, logo }` and drops the array-valued `links`. Read `links` through a
10
- cast on `header` itself instead of the `.links` result, so consumers extending
11
- the layer typecheck cleanly. Runtime behaviour is unchanged.
7
+ - Correct the changelog so it matches what was actually published. `0.1.1` shipped
8
+ with no entry of its own while an entry for a `0.1.2` that was never published
9
+ sat at the top of the file, so consumers had no way to learn from the changelog
10
+ that `0.1.1` contains accessibility fixes for `button-name` and
11
+ `nested-interactive` violations. `0.1.1` now has a complete entry, and the
12
+ `AppHeaderCTA.vue` typecheck fix previously filed under `0.1.2` is recorded
13
+ under `0.1.0`, the release it actually shipped in. No runtime code changed in
14
+ this release.
15
+
16
+ ## 0.1.1
17
+
18
+ ### Minor Changes
19
+
20
+ - Add `header.attribution` so a consuming app can render a partially-linked
21
+ wordmark, e.g. "uxd by UXFront" where only "UXFront" links out. The header now
22
+ owns UHeader's `#left` slot instead of `#title`, because `#title` is rendered
23
+ inside UHeader's own home link and an attribution link there would nest
24
+ anchors. Apps that do not set `attribution` render exactly as before.
25
+
26
+ ### Patch Changes
27
+
28
+ - Fix accessible names on icon-only navigation controls and hide the section
29
+ sub-header when there is nothing to switch between.
30
+
31
+ - The mobile menu toggle and the "copy page" dropdown trigger now expose
32
+ accessible names (sourced from the active locale), clearing two critical
33
+ `button-name` violations that shipped on every page. **Consumers on `0.1.0`
34
+ should upgrade** — those violations were present on every page of every
35
+ site extending this layer.
36
+ - The page footer separator is now `decorative`, clearing a serious
37
+ `nested-interactive` violation caused by focusable links inside an element
38
+ with `role="separator"`.
39
+ - The docs sub-header no longer renders when the consumer declares one section
40
+ or none, so a single-section site gets no empty bar and no extra header
41
+ height.
42
+
43
+ - Skip PostHog initialisation when no key is provisioned
44
+ (`NUXT_PUBLIC_POSTHOG_KEY` unset), instead of initialising a client that fires
45
+ requests at nothing.
46
+
47
+ Note: `0.1.1` was versioned by hand rather than through `changeset version`, so
48
+ it published as a patch even though `header.attribution` is additive and would
49
+ have produced `0.2.0`. The published number stands; the entry above is the
50
+ accurate record of its contents. See UXF-67.
12
51
 
13
52
  ## 0.1.0
14
53
 
@@ -19,3 +58,16 @@
19
58
  polish, base `main.css` (Tailwind + Nuxt UI wiring, palette-free),
20
59
  `MorphingGradientBackground`, and opt-out-gated i18n-redirect and PostHog
21
60
  plugins. Nuxt documentation stack declared as pinned `peerDependencies`.
61
+
62
+ ### Patch Changes
63
+
64
+ - Fix `nuxt typecheck` failure in `AppHeaderCTA.vue`: the config module seeds a
65
+ partial `header` (`{ title }`), so defu narrows the resolved `AppConfig` header
66
+ to `{ title, logo }` and drops the array-valued `links`. Read `links` through a
67
+ cast on `header` itself instead of the `.links` result, so consumers extending
68
+ the layer typecheck cleanly. Runtime behaviour is unchanged.
69
+
70
+ This fix was previously filed under a `## 0.1.2` heading that no published
71
+ release ever carried. It was written while the package was still named
72
+ `@uxfront/docs-theme` — a name that was never published — and it shipped in
73
+ `@uxfront/layer-docs@0.1.0`, the first release under the current name.
package/README.md CHANGED
@@ -42,6 +42,24 @@ export default defineAppConfig({
42
42
  });
43
43
  ```
44
44
 
45
+ ### Brand attribution
46
+
47
+ `header.attribution` renders an optional credit beside the wordmark, where only
48
+ the `label` is a link:
49
+
50
+ ```ts
51
+ export default defineAppConfig({
52
+ header: {
53
+ title: "uxd",
54
+ attribution: { prefix: "by", label: "UXFront", to: "https://uxfront.com" },
55
+ },
56
+ });
57
+ ```
58
+
59
+ That renders `uxd by UXFront` — the wordmark still links home, `by` is plain
60
+ text, and `UXFront` links out. Omit `attribution` (or leave `label` empty) to
61
+ render the wordmark alone.
62
+
45
63
  ## Compatibility
46
64
 
47
65
  Pinned to the Nuxt 4 documentation stack (Nuxt 4.4, Nuxt UI 4.8, Content 3.14,
package/app/app.config.ts CHANGED
@@ -13,6 +13,20 @@ export default defineAppConfig({
13
13
  title: "On this page",
14
14
  },
15
15
 
16
+ header: {
17
+ /**
18
+ * Optional brand attribution rendered beside the header wordmark.
19
+ * `title: "uxd"` plus `{ prefix: "by", label: "UXFront", to: "https://uxfront.com" }`
20
+ * renders "uxd by UXFront" with only "UXFront" linked. An empty `label`
21
+ * renders nothing, which is the neutral default.
22
+ */
23
+ attribution: {
24
+ prefix: "",
25
+ label: "",
26
+ to: "",
27
+ },
28
+ },
29
+
16
30
  /**
17
31
  * Opt-out flags for the layer's client plugins. Defaults keep them on so an
18
32
  * existing consumer is unchanged; a consumer opts out by setting `false`.
@@ -1,11 +1,16 @@
1
1
  <script setup lang="ts">
2
2
  import { motion } from "motion-v";
3
3
  import type { VariantType } from "motion-v";
4
+ import { useLocale } from "@nuxt/ui/composables/useLocale";
4
5
 
5
6
  const props = defineProps<{
6
7
  open: boolean;
7
8
  }>();
8
9
 
10
+ // Same strings Nuxt UI's default `UHeader` toggle uses, so the accessible name
11
+ // stays in sync with the app's locale instead of being hardcoded here.
12
+ const { t } = useLocale();
13
+
9
14
  const variants: {
10
15
  [k: string]: VariantType | ((custom: unknown) => VariantType);
11
16
  } = {
@@ -33,7 +38,14 @@ const state = computed(() => (props.open ? "close" : "normal"));
33
38
  </script>
34
39
 
35
40
  <template>
36
- <UButton size="sm" variant="ghost" color="neutral" class="-me-1.5" square>
41
+ <UButton
42
+ size="sm"
43
+ variant="ghost"
44
+ color="neutral"
45
+ class="-me-1.5"
46
+ square
47
+ :aria-label="open ? t('header.close') : t('header.open')"
48
+ >
37
49
  <svg
38
50
  xmlns="http://www.w3.org/2000/svg"
39
51
  class="size-5"
@@ -43,6 +55,7 @@ const state = computed(() => (props.open ? "close" : "normal"));
43
55
  stroke-width="2"
44
56
  stroke-linecap="round"
45
57
  stroke-linejoin="round"
58
+ aria-hidden="true"
46
59
  >
47
60
  <motion.line
48
61
  x1="4"
@@ -52,8 +52,26 @@ const links = computed(() =>
52
52
  >
53
53
  <AppHeaderCenter />
54
54
 
55
- <template #title>
56
- <AppHeaderLogo class="h-6 w-auto shrink-0" />
55
+ <!--
56
+ `#left` rather than `#title`: UHeader wraps the `#title` slot in its own
57
+ `<ULink :to>` home link, so an attribution link rendered there would be an
58
+ anchor nested in an anchor — invalid HTML that browsers unnest at parse
59
+ time. Owning `#left` lets the wordmark and the attribution sit as
60
+ siblings. The link classes restate UHeader's `title` slot theme, which the
61
+ slot itself does not expose.
62
+ -->
63
+ <template #left>
64
+ <div class="flex shrink-0 items-baseline gap-1.5">
65
+ <ULink
66
+ :to="localePath('/')"
67
+ :aria-label="appConfig.header?.title || site.name"
68
+ class="flex shrink-0 items-end gap-1.5 rounded-xs text-xl font-bold text-highlighted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
69
+ >
70
+ <AppHeaderLogo class="h-6 w-auto shrink-0" />
71
+ </ULink>
72
+
73
+ <AppHeaderAttribution />
74
+ </div>
57
75
  </template>
58
76
 
59
77
  <template #right>
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Optional brand attribution rendered next to the header wordmark, e.g.
4
+ * "uxd by UXFront" where only "UXFront" is a link.
5
+ *
6
+ * Declared by the consuming app as:
7
+ *
8
+ * ```ts
9
+ * header: {
10
+ * title: "uxd",
11
+ * attribution: { prefix: "by", label: "UXFront", to: "https://uxfront.com" },
12
+ * }
13
+ * ```
14
+ *
15
+ * `prefix` stays plain text so the link's accessible name is exactly the brand
16
+ * it points at — "by UXFront" would read as the link text otherwise.
17
+ */
18
+ type Attribution = { prefix?: string; label?: string; to?: string };
19
+
20
+ const appConfig = useAppConfig();
21
+
22
+ // Read through a local type, as `AppHeaderCTA` does for `header.links`: Nuxt's
23
+ // generated `AppConfig` prefers the shape inferred from the merged
24
+ // `app.config.ts` values over the `nuxt.schema.ts` type, so a consumer that
25
+ // declares `header` without `attribution` erases the key from the type even
26
+ // though the layer's own defaults supply it at runtime.
27
+ const attribution = computed(
28
+ () => (appConfig.header as { attribution?: Attribution } | undefined)?.attribution,
29
+ );
30
+ </script>
31
+
32
+ <template>
33
+ <p v-if="attribution?.label" class="shrink-0 text-sm text-muted">
34
+ <!-- Non-breaking space: Vue's `condense` whitespace handling drops a plain
35
+ trailing space before a newline, and the credit should not wrap. -->
36
+ <template v-if="attribution.prefix">{{ attribution.prefix }}&nbsp;</template>
37
+ <ULink
38
+ v-if="attribution.to"
39
+ :to="attribution.to"
40
+ class="rounded-xs font-medium text-toned underline underline-offset-2 hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
41
+ >{{ attribution.label }}</ULink
42
+ >
43
+ <template v-else>{{ attribution.label }}</template>
44
+ </p>
45
+ </template>
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { NavigationMenuItem } from "@nuxt/ui";
3
3
 
4
- const { sections } = useDocsSections();
4
+ const { sections, hasSectionSwitcher } = useDocsSections();
5
5
 
6
6
  const items = computed<NavigationMenuItem[]>(() =>
7
7
  sections.value.map((section) => ({
@@ -13,7 +13,7 @@ const items = computed<NavigationMenuItem[]>(() =>
13
13
  </script>
14
14
 
15
15
  <template>
16
- <div class="border-t border-default max-lg:hidden">
16
+ <div v-if="hasSectionSwitcher" class="border-t border-default max-lg:hidden">
17
17
  <UContainer class="py-1">
18
18
  <UNavigationMenu :items="items" :ui="{ root: 'overflow-x-auto no-scrollbar' }" />
19
19
  </UContainer>
@@ -22,6 +22,7 @@ function select(value: string) {
22
22
  <template>
23
23
  <div class="framework-switcher">
24
24
  <div
25
+ v-if="frameworks.length"
25
26
  class="framework-switcher-tabs flex flex-wrap gap-1 mb-3 border-b border-default"
26
27
  role="tablist"
27
28
  >
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * `UPageHero` on the layer's `MorphingGradientBackground`, usable from MDC:
4
+ *
5
+ * ```mdc
6
+ * ::gradient-page-hero
7
+ * ---
8
+ * ---
9
+ * #title
10
+ * Your headline
11
+ * ::
12
+ * ```
13
+ *
14
+ * Ships with `MorphingGradientBackground` rather than beside it: a package that
15
+ * hands you the ingredient should hand you the recipe too. The gradient's own
16
+ * base surface follows `--ui-bg`; its blob colours are the background
17
+ * component's own fixed palette, overridable per consumer by restating the
18
+ * `--color1..5` custom properties on an ancestor.
19
+ */
20
+ defineOptions({
21
+ inheritAttrs: false,
22
+ });
23
+ </script>
24
+
25
+ <template>
26
+ <MorphingGradientBackground v-slot="{ onMousemove }">
27
+ <UPageHero v-bind="$attrs" @mousemove="onMousemove">
28
+ <slot />
29
+ <template #title><slot name="title" /></template>
30
+ <template #description><slot name="description" /></template>
31
+ <template #headline><slot name="headline" /></template>
32
+ <template #links><slot name="links" /></template>
33
+ </UPageHero>
34
+ </MorphingGradientBackground>
35
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Persistent framework selector above the docs sidebar navigation.
4
+ *
5
+ * Renders nothing unless the consumer declares `docsTheme.frameworks` in its
6
+ * `app.config.ts`. The in-content `FrameworkSwitcher` shares the same state,
7
+ * but per-page tabs alone scale poorly past a handful of frameworks — this is
8
+ * the always-visible control for the same preference.
9
+ */
10
+ const { frameworks } = useFramework();
11
+ const { t } = useDocusI18n();
12
+ </script>
13
+
14
+ <template>
15
+ <div v-if="frameworks.length">
16
+ <span
17
+ class="group relative w-full pr-2.5 py-1.5 flex items-center gap-1.5 text-sm text-muted cursor-default hover:text-highlighted transition-colors font-normal"
18
+ >
19
+ <span class="truncate">{{ t("docs.framework") }}</span>
20
+ </span>
21
+
22
+ <DocsFrameworkSelect class="mb-2" />
23
+ </div>
24
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ const { framework, frameworks } = useFramework();
3
+
4
+ const current = computed(
5
+ () => frameworks.value.find((option) => option.value === framework.value) ?? frameworks.value[0],
6
+ );
7
+ </script>
8
+
9
+ <template>
10
+ <USelect
11
+ v-model="framework"
12
+ :items="[...frameworks]"
13
+ variant="ghost"
14
+ color="neutral"
15
+ :icon="current?.icon"
16
+ class="w-full"
17
+ />
18
+ </template>
@@ -68,6 +68,7 @@ async function copyPage() {
68
68
  color="neutral"
69
69
  variant="soft"
70
70
  class="border-l border-muted"
71
+ :aria-label="t('docs.copy.options')"
71
72
  />
72
73
  </UDropdownMenu>
73
74
  </UFieldGroup>
@@ -46,5 +46,12 @@ export function useDocsSections() {
46
46
 
47
47
  const activeSection = computed(() => sections.value.find((section) => section.active));
48
48
 
49
- return { sections, activeSection };
49
+ /**
50
+ * Whether the section switcher is worth rendering. A single section (or none)
51
+ * gives the reader nothing to switch between, so the sub-header is suppressed
52
+ * entirely rather than rendering an empty bar.
53
+ */
54
+ const hasSectionSwitcher = computed(() => sections.value.length > 1);
55
+
56
+ return { sections, activeSection, hasSectionSwitcher };
50
57
  }
@@ -1,11 +1,11 @@
1
1
  import { useLocalStorage } from "@vueuse/core";
2
2
 
3
3
  /**
4
- * A single selectable framework tab in the docs `FrameworkSwitcher`.
5
- * `value` doubles as the slot name and the persisted storage token; `label` is
6
- * the visible tab text; `icon` is an Iconify name. `value` is an open string so
7
- * a consumer can declare any framework set (e.g. `svelte`, `solid`, `angular`),
8
- * not just the layer's neutral default.
4
+ * A single selectable framework in the docs `FrameworkSwitcher` and the sidebar
5
+ * `DocsFrameworkSelect`. `value` doubles as the slot name and the persisted
6
+ * storage token; `label` is the visible text; `icon` is an Iconify name.
7
+ * `value` is an open string so a consumer can declare any framework set
8
+ * (e.g. `svelte`, `solid`, `angular`).
9
9
  */
10
10
  export interface FrameworkOption {
11
11
  value: string;
@@ -13,9 +13,12 @@ export interface FrameworkOption {
13
13
  icon: string;
14
14
  }
15
15
 
16
+ // Neutral, layer-owned storage key — not tied to any consuming product.
17
+ const STORAGE_KEY = "docs-theme:framework";
18
+
16
19
  /**
17
- * Neutral, brand-agnostic default framework set. A consumer overrides the whole
18
- * list — in declared order — via its `app.config.ts`:
20
+ * Reads the framework list a consumer declares — in display order — in its
21
+ * `app.config.ts`:
19
22
  *
20
23
  * ```ts
21
24
  * export default defineAppConfig({
@@ -24,42 +27,30 @@ export interface FrameworkOption {
24
27
  * { value: "react", label: "React", icon: "i-mdi-react" },
25
28
  * { value: "vue", label: "Vue", icon: "i-mdi-vuejs" },
26
29
  * { value: "svelte", label: "Svelte", icon: "i-mdi-svelte" },
27
- * // ...N frameworks
28
30
  * ],
29
31
  * },
30
32
  * });
31
33
  * ```
32
34
  *
35
+ * `docsTheme.frameworks` is the only source. Which frameworks a product
36
+ * documents is the product's fact, not the theme's, so the layer ships no
37
+ * default list; a consumer that declares none gets an empty list and the
38
+ * framework UI renders nothing.
39
+ *
33
40
  * The list is read from app config rather than merged as a layer default on
34
41
  * purpose: Nuxt's `defu` layer merge *concatenates* arrays, so a shipped default
35
- * would append to (not replace) the consumer's list. Keeping the default here
36
- * and only reading a consumer-supplied override guarantees a clean replacement,
37
- * while an app with no `frameworks` configured keeps the original behaviour.
42
+ * would append to (not replace) the consumer's list.
38
43
  */
39
- export const DEFAULT_FRAMEWORKS: readonly FrameworkOption[] = [
40
- { value: "react", label: "React", icon: "i-mdi-react" },
41
- {
42
- value: "other",
43
- label: "Vanilla",
44
- icon: "i-mdi-language-typescript",
45
- },
46
- { value: "vue", label: "Vue", icon: "i-mdi-vuejs" },
47
- ] as const;
48
-
49
- // Neutral, layer-owned storage key — not tied to any consuming product.
50
- const STORAGE_KEY = "docs-theme:framework";
51
-
52
44
  export const useFramework = () => {
53
45
  const appConfig = useAppConfig() as {
54
46
  docsTheme?: { frameworks?: FrameworkOption[] };
55
47
  };
56
48
 
57
- const frameworks = computed<readonly FrameworkOption[]>(() => {
58
- const configured = appConfig.docsTheme?.frameworks;
59
- return configured?.length ? configured : DEFAULT_FRAMEWORKS;
60
- });
49
+ const frameworks = computed<readonly FrameworkOption[]>(
50
+ () => appConfig.docsTheme?.frameworks ?? [],
51
+ );
61
52
 
62
- const framework = useLocalStorage<string>(STORAGE_KEY, frameworks.value[0]!.value, {
53
+ const framework = useLocalStorage<string>(STORAGE_KEY, frameworks.value[0]?.value ?? "", {
63
54
  initOnMounted: true,
64
55
  });
65
56
 
@@ -11,6 +11,7 @@ const isDocs = computed(() => route.path.startsWith("/docs"));
11
11
  <UPage>
12
12
  <template #left>
13
13
  <UPageAside>
14
+ <DocsAsideLeftTop />
14
15
  <DocsAsideLeftBody />
15
16
  </UPageAside>
16
17
  </template>
@@ -128,7 +128,10 @@ const editLink = computed(() => {
128
128
  <UPageBody>
129
129
  <ContentRenderer v-if="page" :value="page" />
130
130
 
131
- <USeparator>
131
+ <!-- `decorative` drops the `separator` role: the rule is decoration around
132
+ the edit/report actions, and a separator with focusable descendants is
133
+ a `nested-interactive` violation. -->
134
+ <USeparator decorative>
132
135
  <div v-if="github" class="flex items-center gap-2 text-sm text-muted">
133
136
  <UButton
134
137
  variant="link"
@@ -14,6 +14,12 @@ export default defineNuxtPlugin(() => {
14
14
 
15
15
  const runtimeConfig = useRuntimeConfig();
16
16
 
17
+ // No key provisioned (`NUXT_PUBLIC_POSTHOG_KEY` unset) — no-op instead of
18
+ // initialising a client that would fire requests at nothing.
19
+ if (!runtimeConfig.public.posthog.key) {
20
+ return;
21
+ }
22
+
17
23
  const posthogClient = posthog.init(runtimeConfig.public.posthog.key, {
18
24
  api_host: runtimeConfig.public.posthog.host,
19
25
  defaults: runtimeConfig.public.posthog.defaults as ConfigDefaults,
@@ -0,0 +1,24 @@
1
+ {
2
+ "common": {
3
+ "or": "أو",
4
+ "error": {
5
+ "title": "الصفحة غير موجودة",
6
+ "description": "نأسف، لكن الصفحة التي تبحث عنها غير موجودة."
7
+ }
8
+ },
9
+ "docs": {
10
+ "copy": {
11
+ "page": "نسخ الصفحة",
12
+ "options": "خيارات نسخ إضافية",
13
+ "link": "نسخ صفحة Markdown",
14
+ "view": "عرض كـ Markdown",
15
+ "gpt": "فتح في ChatGPT",
16
+ "claude": "فتح في Claude"
17
+ },
18
+ "framework": "إطار العمل",
19
+ "links": "المجتمع",
20
+ "toc": "في هذه الصفحة",
21
+ "report": "الإبلاغ عن مشكلة",
22
+ "edit": "تحرير هذه الصفحة"
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "common": {
3
+ "or": "або",
4
+ "error": {
5
+ "title": "Старонка не знойдзена",
6
+ "description": "Прабачце, але старонка, якую вы шукаеце, не знойдзена."
7
+ }
8
+ },
9
+ "docs": {
10
+ "copy": {
11
+ "page": "Скапіраваць старонку",
12
+ "options": "Дадатковыя параметры капіравання",
13
+ "link": "Скапіраваць Markdown-старонку",
14
+ "view": "Праглядзець як Markdown",
15
+ "gpt": "Адкрыць у ChatGPT",
16
+ "claude": "Адкрыць у Claude"
17
+ },
18
+ "framework": "Фреймворк",
19
+ "links": "Супольнасць",
20
+ "toc": "На гэтай старонцы",
21
+ "report": "Паведаміць пра праблему",
22
+ "edit": "Рэдагаваць гэтую старонку"
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "common": {
3
+ "or": "অথবা",
4
+ "error": {
5
+ "title": "পেজ খুঁজে পাওয়া যায়নি",
6
+ "description": "দুঃখিত, আপনি যে পেজটি খুঁজছেন সেটি পাওয়া যায়নি।"
7
+ }
8
+ },
9
+ "docs": {
10
+ "copy": {
11
+ "page": "পেজ কপি করুন",
12
+ "options": "আরও কপি অপশন",
13
+ "link": "Markdown পেজ কপি করুন",
14
+ "view": "Markdown হিসেবে দেখুন",
15
+ "gpt": "ChatGPT এ খুলুন",
16
+ "claude": "Claude এ খুলুন"
17
+ },
18
+ "framework": "ফ্রেমওয়ার্ক",
19
+ "links": "কমিউনিটি",
20
+ "toc": "এই পেজে",
21
+ "report": "সমস্যা রিপোর্ট করুন",
22
+ "edit": "এই পেজ সম্পাদনা করুন"
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "common": {
3
+ "or": "o",
4
+ "error": {
5
+ "title": "Pàgina no trobada",
6
+ "description": "Ho sentim, però la pàgina que cerques no existeix."
7
+ }
8
+ },
9
+ "docs": {
10
+ "copy": {
11
+ "page": "Copiar pàgina",
12
+ "options": "Més opcions de còpia",
13
+ "link": "Copiar pàgina Markdown",
14
+ "view": "Veure com a Markdown",
15
+ "gpt": "Obrir a ChatGPT",
16
+ "claude": "Obrir a Claude"
17
+ },
18
+ "framework": "Framework",
19
+ "links": "Comunitat",
20
+ "toc": "En aquesta pàgina",
21
+ "report": "Informar d'un problema",
22
+ "edit": "Editar aquesta pàgina"
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "common": {
3
+ "or": "یان",
4
+ "error": {
5
+ "title": "پەڕە نەدۆزرایەوە",
6
+ "description": "بەداخەوە، ئەم پەڕەیە نەدۆزرایەوە."
7
+ }
8
+ },
9
+ "docs": {
10
+ "copy": {
11
+ "page": "کۆپیکردنی پەڕە",
12
+ "options": "بژاردەی زیاتری لەبەرگرتنەوە",
13
+ "link": "کۆپیکردنی پەڕەی Markdown",
14
+ "view": "بینین وەک Markdown",
15
+ "gpt": "کردنەوە لە ChatGPT",
16
+ "claude": "کردنەوە لە Claude"
17
+ },
18
+ "framework": "چوارچێوە",
19
+ "links": "کۆمەڵگا",
20
+ "toc": "لەم پەڕەدا",
21
+ "report": "ڕاپۆرتکردنی کێشە",
22
+ "edit": "دەستکاریکردنی ئەم پەڕەیە"
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "common": {
3
+ "or": "nebo",
4
+ "error": {
5
+ "title": "Stránka nenalezena",
6
+ "description": "Je nám líto, ale stránka, kterou hledáte, nebyla nalezena."
7
+ }
8
+ },
9
+ "docs": {
10
+ "copy": {
11
+ "page": "Kopírovat stránku",
12
+ "options": "Další možnosti kopírování",
13
+ "link": "Kopírovat Markdown stránku",
14
+ "view": "Zobrazit jako Markdown",
15
+ "gpt": "Otevřít v ChatGPT",
16
+ "claude": "Otevřít v Claude"
17
+ },
18
+ "framework": "Framework",
19
+ "links": "Komunita",
20
+ "toc": "Na této stránce",
21
+ "report": "Nahlásit problém",
22
+ "edit": "Upravit tuto stránku"
23
+ }
24
+ }