From c6e2baecee34e26d09b37e2ab20c38cb940aadd2 Mon Sep 17 00:00:00 2001 From: Ahmet ALMAZ <5708563+Music47ell@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:26:20 +0300 Subject: [PATCH] feat(i18n): add Arabic translation for all 170 questions --- CONTRIBUTING.md | 2 +- docs/.vitepress/config.ts | 101 ++++- docs/.vitepress/json-ld.ts | 7 +- docs/.vitepress/og-image.ts | 45 +- docs/.vitepress/sidebar.ts | 46 +- docs/.vitepress/theme/QuestionMeta.vue | 2 +- docs/.vitepress/theme/i18n.ts | 3 +- docs/.vitepress/theme/questions.data.ts | 4 +- docs/.vitepress/theme/style.css | 111 +++++ docs/ar/flashcards.md | 6 + docs/ar/index.md | 34 ++ docs/ar/q/abort-controller-composable.md | 234 +++++++++++ docs/ar/q/accessibility-in-vue.md | 270 ++++++++++++ docs/ar/q/async-components.md | 117 ++++++ docs/ar/q/async-composable-error-handling.md | 268 ++++++++++++ docs/ar/q/auth-with-vue-router.md | 231 ++++++++++ docs/ar/q/await-in-script-setup.md | 233 +++++++++++ docs/ar/q/compiler-macros.md | 200 +++++++++ docs/ar/q/complex-forms.md | 224 ++++++++++ docs/ar/q/component-vs-element-vnode.md | 113 +++++ docs/ar/q/composable-data-fetching.md | 111 +++++ docs/ar/q/composition-api-vs-options-api.md | 105 +++++ docs/ar/q/composition-api-vs-react-hooks.md | 143 +++++++ docs/ar/q/compound-components-pattern.md | 357 ++++++++++++++++ .../ar/q/computed-conditional-dependencies.md | 70 ++++ docs/ar/q/computed-sort-mutation.md | 62 +++ docs/ar/q/computed-vs-watch.md | 106 +++++ docs/ar/q/conditional-rendering.md | 132 ++++++ docs/ar/q/container-presentational-pattern.md | 196 +++++++++ .../ar/q/controlled-vs-uncontrolled-inputs.md | 126 ++++++ docs/ar/q/create-vue-project.md | 132 ++++++ docs/ar/q/css-scoped-modules-dynamic.md | 134 ++++++ docs/ar/q/custom-directives.md | 176 ++++++++ docs/ar/q/custom-ref.md | 149 +++++++ docs/ar/q/custom-v-model-modifiers.md | 242 +++++++++++ docs/ar/q/dashboard-widget-architecture.md | 307 ++++++++++++++ docs/ar/q/debounce-search-input.md | 127 ++++++ docs/ar/q/debug-ssr-requests.md | 180 ++++++++ docs/ar/q/define-expose.md | 184 ++++++++ docs/ar/q/define-slots-typing.md | 274 ++++++++++++ docs/ar/q/definemodel-object-mutation.md | 84 ++++ docs/ar/q/defineprops-type-limitations.md | 218 ++++++++++ docs/ar/q/diagnose-slow-page.md | 199 +++++++++ docs/ar/q/dom-update-batching.md | 138 ++++++ docs/ar/q/drag-and-drop.md | 363 ++++++++++++++++ docs/ar/q/dynamic-components-keepalive.md | 196 +++++++++ docs/ar/q/effect-scope.md | 154 +++++++ docs/ar/q/emit-events-typescript.md | 123 ++++++ docs/ar/q/error-handling.md | 189 +++++++++ docs/ar/q/event-handling.md | 177 ++++++++ docs/ar/q/event-modifier-order.md | 122 ++++++ docs/ar/q/fallthrough-attrs.md | 152 +++++++ docs/ar/q/flux-unidirectional-data-flow.md | 219 ++++++++++ docs/ar/q/focus-management-spa.md | 292 +++++++++++++ docs/ar/q/force-rerender.md | 131 ++++++ docs/ar/q/functional-components.md | 103 +++++ docs/ar/q/generic-components.md | 222 ++++++++++ docs/ar/q/get-current-instance.md | 116 ++++++ docs/ar/q/global-error-handling.md | 302 ++++++++++++++ docs/ar/q/hoc-equivalents-vue.md | 210 ++++++++++ docs/ar/q/how-pinia-works.md | 208 +++++++++ docs/ar/q/how-vuex-works.md | 137 ++++++ docs/ar/q/i18n-vue.md | 288 +++++++++++++ docs/ar/q/infinite-scroll.md | 330 +++++++++++++++ docs/ar/q/initialize-state-from-prop.md | 207 +++++++++ docs/ar/q/is-attribute-dynamic-rendering.md | 207 +++++++++ docs/ar/q/javascript-before-vue.md | 240 +++++++++++ docs/ar/q/large-project-structure.md | 163 ++++++++ docs/ar/q/lazy-loading-code-splitting.md | 204 +++++++++ docs/ar/q/lifecycle-hooks.md | 95 +++++ docs/ar/q/lifting-state-up.md | 145 +++++++ docs/ar/q/list-rendering-v-for.md | 173 ++++++++ docs/ar/q/list-virtualization.md | 227 ++++++++++ docs/ar/q/markraw-toraw.md | 125 ++++++ docs/ar/q/micro-frontends-vue.md | 199 +++++++++ docs/ar/q/multiple-v-model.md | 204 +++++++++ docs/ar/q/mutating-props-warning.md | 96 +++++ docs/ar/q/nexttick.md | 126 ++++++ docs/ar/q/nuxt-authentication.md | 230 ++++++++++ docs/ar/q/nuxt-auto-imports.md | 196 +++++++++ docs/ar/q/nuxt-data-fetching.md | 143 +++++++ docs/ar/q/nuxt-deployment.md | 158 +++++++ docs/ar/q/nuxt-directory-structure.md | 181 ++++++++ docs/ar/q/nuxt-file-based-routing.md | 129 ++++++ docs/ar/q/nuxt-hybrid-rendering.md | 127 ++++++ docs/ar/q/nuxt-layers.md | 161 +++++++ docs/ar/q/nuxt-layouts.md | 187 +++++++++ docs/ar/q/nuxt-middleware.md | 137 ++++++ docs/ar/q/nuxt-modules.md | 146 +++++++ docs/ar/q/nuxt-nitro-server-routes.md | 177 ++++++++ docs/ar/q/nuxt-payload.md | 171 ++++++++ docs/ar/q/nuxt-rendering-modes.md | 123 ++++++ docs/ar/q/nuxt-server-vs-route-middleware.md | 175 ++++++++ docs/ar/q/nuxt-state-management.md | 162 ++++++++ docs/ar/q/nuxt-usefetch-event-handler.md | 153 +++++++ docs/ar/q/nuxt2-to-nuxt3-friction.md | 191 +++++++++ docs/ar/q/nuxt2-vs-nuxt3.md | 211 ++++++++++ docs/ar/q/object-freeze-reactive.md | 164 ++++++++ docs/ar/q/optimistic-updates.md | 261 ++++++++++++ docs/ar/q/perf-component-abstraction-lists.md | 152 +++++++ docs/ar/q/perf-computed-object-stability.md | 211 ++++++++++ docs/ar/q/perf-props-stability.md | 211 ++++++++++ docs/ar/q/perf-updated-hook.md | 256 ++++++++++++ docs/ar/q/performance-optimization.md | 393 ++++++++++++++++++ docs/ar/q/pinia-colada.md | 184 ++++++++ docs/ar/q/pinia-plugins.md | 217 ++++++++++ docs/ar/q/pinia-vs-composable-vs-local.md | 205 +++++++++ docs/ar/q/pinia-vs-vuex.md | 106 +++++ docs/ar/q/plugin-system.md | 153 +++++++ docs/ar/q/props-vs-state.md | 137 ++++++ docs/ar/q/props-with-typescript.md | 168 ++++++++ docs/ar/q/provide-inject.md | 113 +++++ docs/ar/q/proxy-identity-hazard.md | 126 ++++++ docs/ar/q/reactive-destructuring-gotcha.md | 70 ++++ docs/ar/q/reactive-map-set.md | 203 +++++++++ docs/ar/q/reactive-property-reassignment.md | 128 ++++++ docs/ar/q/reactive-with-primitives.md | 48 +++ docs/ar/q/reactivity-system.md | 88 ++++ docs/ar/q/readonly-shallow-readonly.md | 151 +++++++ docs/ar/q/ref-empty-string-vs-null.md | 128 ++++++ docs/ar/q/ref-value-gotcha.md | 60 +++ docs/ar/q/ref-vs-reactive.md | 104 +++++ docs/ar/q/render-functions-jsx.md | 146 +++++++ docs/ar/q/scale-vue-multiple-teams.md | 165 ++++++++ docs/ar/q/scoped-css-child-components.md | 130 ++++++ docs/ar/q/script-setup.md | 156 +++++++ docs/ar/q/shallow-ref-reactive.md | 89 ++++ docs/ar/q/shared-component-library.md | 121 ++++++ docs/ar/q/shopping-cart-architecture.md | 359 ++++++++++++++++ docs/ar/q/slots.md | 144 +++++++ docs/ar/q/spreading-props-pitfalls.md | 242 +++++++++++ .../ar/q/ssr-cross-request-state-pollution.md | 132 ++++++ docs/ar/q/ssr-hydration-mismatch.md | 105 +++++ docs/ar/q/ssr-platform-specific-apis.md | 221 ++++++++++ docs/ar/q/suspense-ssr-hydration.md | 218 ++++++++++ docs/ar/q/suspense.md | 212 ++++++++++ docs/ar/q/teleport-fragments-suspense.md | 133 ++++++ docs/ar/q/teleport-scoped-styles.md | 158 +++++++ docs/ar/q/template-ref-null-v-if.md | 103 +++++ docs/ar/q/template-refs.md | 244 +++++++++++ docs/ar/q/test-async-setup-suspense.md | 214 ++++++++++ docs/ar/q/test-composable-fake-timers.md | 184 ++++++++ docs/ar/q/test-pinia-store.md | 239 +++++++++++ docs/ar/q/testing-composable-fetch.md | 59 +++ docs/ar/q/testing-with-vitest.md | 188 +++++++++ docs/ar/q/torefs-toref-tovalue.md | 145 +++++++ docs/ar/q/transition-and-transition-group.md | 252 +++++++++++ docs/ar/q/tree-shaking-vue3.md | 140 +++++++ docs/ar/q/typing-event-handlers.md | 212 ++++++++++ docs/ar/q/typing-template-refs.md | 245 +++++++++++ docs/ar/q/undeclared-emits-double-firing.md | 91 ++++ docs/ar/q/v-for-key.md | 103 +++++ docs/ar/q/v-html-xss.md | 210 ++++++++++ docs/ar/q/v-if-vs-v-show.md | 71 ++++ docs/ar/q/v-if-with-v-for.md | 96 +++++ docs/ar/q/v-model-basics.md | 206 +++++++++ docs/ar/q/v-model-custom-components.md | 152 +++++++ docs/ar/q/v-model-explained-for-react-devs.md | 220 ++++++++++ docs/ar/q/v-once-v-memo.md | 178 ++++++++ docs/ar/q/v-show-template-limitation.md | 89 ++++ docs/ar/q/virtual-dom.md | 107 +++++ docs/ar/q/vitest-vue-config.md | 222 ++++++++++ docs/ar/q/vue-anti-patterns.md | 272 ++++++++++++ docs/ar/q/vue-devtools.md | 111 +++++ docs/ar/q/vue-library-or-framework.md | 104 +++++ docs/ar/q/vue-router-navigation-guards.md | 154 +++++++ docs/ar/q/vue-typescript-utility-types.md | 176 ++++++++ docs/ar/q/vue2-to-vue3-migration.md | 173 ++++++++ docs/ar/q/vueuse.md | 213 ++++++++++ docs/ar/q/vuex-module-to-pinia.md | 152 +++++++ docs/ar/q/watch-async-memory-leak.md | 80 ++++ docs/ar/q/watch-deep-same-reference.md | 83 ++++ docs/ar/q/watch-vs-watcheffect.md | 110 +++++ docs/ar/q/watcheffect-async-tracking.md | 75 ++++ docs/ar/q/what-is-a-composable.md | 127 ++++++ docs/ar/q/what-is-hydration.md | 201 +++++++++ docs/ar/q/what-is-ssr.md | 127 ++++++ docs/ar/q/what-is-vite.md | 146 +++++++ docs/ar/q/what-is-vue.md | 98 +++++ docs/ar/q/withdefaults-gotchas.md | 222 ++++++++++ docs/ar/q/writable-computed.md | 162 ++++++++ docs/ar/questions.md | 6 + docs/ar/resources.md | 55 +++ docs/es/index.md | 4 +- docs/index.md | 4 +- 185 files changed, 29377 insertions(+), 22 deletions(-) create mode 100644 docs/ar/flashcards.md create mode 100644 docs/ar/index.md create mode 100644 docs/ar/q/abort-controller-composable.md create mode 100644 docs/ar/q/accessibility-in-vue.md create mode 100644 docs/ar/q/async-components.md create mode 100644 docs/ar/q/async-composable-error-handling.md create mode 100644 docs/ar/q/auth-with-vue-router.md create mode 100644 docs/ar/q/await-in-script-setup.md create mode 100644 docs/ar/q/compiler-macros.md create mode 100644 docs/ar/q/complex-forms.md create mode 100644 docs/ar/q/component-vs-element-vnode.md create mode 100644 docs/ar/q/composable-data-fetching.md create mode 100644 docs/ar/q/composition-api-vs-options-api.md create mode 100644 docs/ar/q/composition-api-vs-react-hooks.md create mode 100644 docs/ar/q/compound-components-pattern.md create mode 100644 docs/ar/q/computed-conditional-dependencies.md create mode 100644 docs/ar/q/computed-sort-mutation.md create mode 100644 docs/ar/q/computed-vs-watch.md create mode 100644 docs/ar/q/conditional-rendering.md create mode 100644 docs/ar/q/container-presentational-pattern.md create mode 100644 docs/ar/q/controlled-vs-uncontrolled-inputs.md create mode 100644 docs/ar/q/create-vue-project.md create mode 100644 docs/ar/q/css-scoped-modules-dynamic.md create mode 100644 docs/ar/q/custom-directives.md create mode 100644 docs/ar/q/custom-ref.md create mode 100644 docs/ar/q/custom-v-model-modifiers.md create mode 100644 docs/ar/q/dashboard-widget-architecture.md create mode 100644 docs/ar/q/debounce-search-input.md create mode 100644 docs/ar/q/debug-ssr-requests.md create mode 100644 docs/ar/q/define-expose.md create mode 100644 docs/ar/q/define-slots-typing.md create mode 100644 docs/ar/q/definemodel-object-mutation.md create mode 100644 docs/ar/q/defineprops-type-limitations.md create mode 100644 docs/ar/q/diagnose-slow-page.md create mode 100644 docs/ar/q/dom-update-batching.md create mode 100644 docs/ar/q/drag-and-drop.md create mode 100644 docs/ar/q/dynamic-components-keepalive.md create mode 100644 docs/ar/q/effect-scope.md create mode 100644 docs/ar/q/emit-events-typescript.md create mode 100644 docs/ar/q/error-handling.md create mode 100644 docs/ar/q/event-handling.md create mode 100644 docs/ar/q/event-modifier-order.md create mode 100644 docs/ar/q/fallthrough-attrs.md create mode 100644 docs/ar/q/flux-unidirectional-data-flow.md create mode 100644 docs/ar/q/focus-management-spa.md create mode 100644 docs/ar/q/force-rerender.md create mode 100644 docs/ar/q/functional-components.md create mode 100644 docs/ar/q/generic-components.md create mode 100644 docs/ar/q/get-current-instance.md create mode 100644 docs/ar/q/global-error-handling.md create mode 100644 docs/ar/q/hoc-equivalents-vue.md create mode 100644 docs/ar/q/how-pinia-works.md create mode 100644 docs/ar/q/how-vuex-works.md create mode 100644 docs/ar/q/i18n-vue.md create mode 100644 docs/ar/q/infinite-scroll.md create mode 100644 docs/ar/q/initialize-state-from-prop.md create mode 100644 docs/ar/q/is-attribute-dynamic-rendering.md create mode 100644 docs/ar/q/javascript-before-vue.md create mode 100644 docs/ar/q/large-project-structure.md create mode 100644 docs/ar/q/lazy-loading-code-splitting.md create mode 100644 docs/ar/q/lifecycle-hooks.md create mode 100644 docs/ar/q/lifting-state-up.md create mode 100644 docs/ar/q/list-rendering-v-for.md create mode 100644 docs/ar/q/list-virtualization.md create mode 100644 docs/ar/q/markraw-toraw.md create mode 100644 docs/ar/q/micro-frontends-vue.md create mode 100644 docs/ar/q/multiple-v-model.md create mode 100644 docs/ar/q/mutating-props-warning.md create mode 100644 docs/ar/q/nexttick.md create mode 100644 docs/ar/q/nuxt-authentication.md create mode 100644 docs/ar/q/nuxt-auto-imports.md create mode 100644 docs/ar/q/nuxt-data-fetching.md create mode 100644 docs/ar/q/nuxt-deployment.md create mode 100644 docs/ar/q/nuxt-directory-structure.md create mode 100644 docs/ar/q/nuxt-file-based-routing.md create mode 100644 docs/ar/q/nuxt-hybrid-rendering.md create mode 100644 docs/ar/q/nuxt-layers.md create mode 100644 docs/ar/q/nuxt-layouts.md create mode 100644 docs/ar/q/nuxt-middleware.md create mode 100644 docs/ar/q/nuxt-modules.md create mode 100644 docs/ar/q/nuxt-nitro-server-routes.md create mode 100644 docs/ar/q/nuxt-payload.md create mode 100644 docs/ar/q/nuxt-rendering-modes.md create mode 100644 docs/ar/q/nuxt-server-vs-route-middleware.md create mode 100644 docs/ar/q/nuxt-state-management.md create mode 100644 docs/ar/q/nuxt-usefetch-event-handler.md create mode 100644 docs/ar/q/nuxt2-to-nuxt3-friction.md create mode 100644 docs/ar/q/nuxt2-vs-nuxt3.md create mode 100644 docs/ar/q/object-freeze-reactive.md create mode 100644 docs/ar/q/optimistic-updates.md create mode 100644 docs/ar/q/perf-component-abstraction-lists.md create mode 100644 docs/ar/q/perf-computed-object-stability.md create mode 100644 docs/ar/q/perf-props-stability.md create mode 100644 docs/ar/q/perf-updated-hook.md create mode 100644 docs/ar/q/performance-optimization.md create mode 100644 docs/ar/q/pinia-colada.md create mode 100644 docs/ar/q/pinia-plugins.md create mode 100644 docs/ar/q/pinia-vs-composable-vs-local.md create mode 100644 docs/ar/q/pinia-vs-vuex.md create mode 100644 docs/ar/q/plugin-system.md create mode 100644 docs/ar/q/props-vs-state.md create mode 100644 docs/ar/q/props-with-typescript.md create mode 100644 docs/ar/q/provide-inject.md create mode 100644 docs/ar/q/proxy-identity-hazard.md create mode 100644 docs/ar/q/reactive-destructuring-gotcha.md create mode 100644 docs/ar/q/reactive-map-set.md create mode 100644 docs/ar/q/reactive-property-reassignment.md create mode 100644 docs/ar/q/reactive-with-primitives.md create mode 100644 docs/ar/q/reactivity-system.md create mode 100644 docs/ar/q/readonly-shallow-readonly.md create mode 100644 docs/ar/q/ref-empty-string-vs-null.md create mode 100644 docs/ar/q/ref-value-gotcha.md create mode 100644 docs/ar/q/ref-vs-reactive.md create mode 100644 docs/ar/q/render-functions-jsx.md create mode 100644 docs/ar/q/scale-vue-multiple-teams.md create mode 100644 docs/ar/q/scoped-css-child-components.md create mode 100644 docs/ar/q/script-setup.md create mode 100644 docs/ar/q/shallow-ref-reactive.md create mode 100644 docs/ar/q/shared-component-library.md create mode 100644 docs/ar/q/shopping-cart-architecture.md create mode 100644 docs/ar/q/slots.md create mode 100644 docs/ar/q/spreading-props-pitfalls.md create mode 100644 docs/ar/q/ssr-cross-request-state-pollution.md create mode 100644 docs/ar/q/ssr-hydration-mismatch.md create mode 100644 docs/ar/q/ssr-platform-specific-apis.md create mode 100644 docs/ar/q/suspense-ssr-hydration.md create mode 100644 docs/ar/q/suspense.md create mode 100644 docs/ar/q/teleport-fragments-suspense.md create mode 100644 docs/ar/q/teleport-scoped-styles.md create mode 100644 docs/ar/q/template-ref-null-v-if.md create mode 100644 docs/ar/q/template-refs.md create mode 100644 docs/ar/q/test-async-setup-suspense.md create mode 100644 docs/ar/q/test-composable-fake-timers.md create mode 100644 docs/ar/q/test-pinia-store.md create mode 100644 docs/ar/q/testing-composable-fetch.md create mode 100644 docs/ar/q/testing-with-vitest.md create mode 100644 docs/ar/q/torefs-toref-tovalue.md create mode 100644 docs/ar/q/transition-and-transition-group.md create mode 100644 docs/ar/q/tree-shaking-vue3.md create mode 100644 docs/ar/q/typing-event-handlers.md create mode 100644 docs/ar/q/typing-template-refs.md create mode 100644 docs/ar/q/undeclared-emits-double-firing.md create mode 100644 docs/ar/q/v-for-key.md create mode 100644 docs/ar/q/v-html-xss.md create mode 100644 docs/ar/q/v-if-vs-v-show.md create mode 100644 docs/ar/q/v-if-with-v-for.md create mode 100644 docs/ar/q/v-model-basics.md create mode 100644 docs/ar/q/v-model-custom-components.md create mode 100644 docs/ar/q/v-model-explained-for-react-devs.md create mode 100644 docs/ar/q/v-once-v-memo.md create mode 100644 docs/ar/q/v-show-template-limitation.md create mode 100644 docs/ar/q/virtual-dom.md create mode 100644 docs/ar/q/vitest-vue-config.md create mode 100644 docs/ar/q/vue-anti-patterns.md create mode 100644 docs/ar/q/vue-devtools.md create mode 100644 docs/ar/q/vue-library-or-framework.md create mode 100644 docs/ar/q/vue-router-navigation-guards.md create mode 100644 docs/ar/q/vue-typescript-utility-types.md create mode 100644 docs/ar/q/vue2-to-vue3-migration.md create mode 100644 docs/ar/q/vueuse.md create mode 100644 docs/ar/q/vuex-module-to-pinia.md create mode 100644 docs/ar/q/watch-async-memory-leak.md create mode 100644 docs/ar/q/watch-deep-same-reference.md create mode 100644 docs/ar/q/watch-vs-watcheffect.md create mode 100644 docs/ar/q/watcheffect-async-tracking.md create mode 100644 docs/ar/q/what-is-a-composable.md create mode 100644 docs/ar/q/what-is-hydration.md create mode 100644 docs/ar/q/what-is-ssr.md create mode 100644 docs/ar/q/what-is-vite.md create mode 100644 docs/ar/q/what-is-vue.md create mode 100644 docs/ar/q/withdefaults-gotchas.md create mode 100644 docs/ar/q/writable-computed.md create mode 100644 docs/ar/questions.md create mode 100644 docs/ar/resources.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d33bc1a..e096672 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ If your question needs a new tag, propose it in the PR description. English is the source of truth. Translated files live in `docs/{lang}/q/` mirroring `docs/q/`. -Currently supported: English (en), Spanish (es). Looking for contributors for: Chinese (zh), Portuguese (pt), Japanese (ja), Korean (ko), French (fr), German (de), Russian (ru), Arabic (ar), Vietnamese (vi), Hindi (hi). See the [translation issues](https://github.com/miguelfernandezdev/vuewiki.dev/issues?q=label%3Aenhancement+i18n) for details. +Currently supported: English (en), Spanish (es), Arabic (ar). Looking for contributors for: Chinese (zh), Portuguese (pt), Japanese (ja), Korean (ko), French (fr), German (de), Russian (ru), Vietnamese (vi), Hindi (hi). See the [translation issues](https://github.com/miguelfernandezdev/vuewiki.dev/issues?q=label%3Aenhancement+i18n) for details. 1. Pick a question file from `docs/{lang}/q/` that still has English content or needs improvement. 1. Translate the markdown body. Keep code examples in English, translate surrounding text. diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 1a4992e..547f8cc 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -38,7 +38,8 @@ export default defineConfig({ const isHome = pageData.relativePath === 'index.md' || - pageData.relativePath === 'es/index.md' + pageData.relativePath === 'es/index.md' || + pageData.relativePath === 'ar/index.md' const title = isHome ? 'VueWiki — 170+ Vue.js Interview Questions with Answers' : pageData.frontmatter.title || 'VueWiki' @@ -69,7 +70,7 @@ export default defineConfig({ .replace(/\.md$/, '') .replaceAll('/', '-') const outDir = siteConfig.outDir - const lang = pageData.relativePath.startsWith('es/') ? 'es' : 'en' + const lang = pageData.relativePath.startsWith('es/') ? 'es' : pageData.relativePath.startsWith('ar/') ? 'ar' : 'en' const filename = await generateOgImage( slug, pageData.frontmatter.title, @@ -90,18 +91,31 @@ export default defineConfig({ if (pageData.relativePath.startsWith('q/')) { const esUrl = `${siteUrl}/es/${path}` + const arUrl = `${siteUrl}/ar/${path}` head.push([ 'link', { rel: 'alternate', hreflang: 'en', href: canonicalUrl } ]) head.push(['link', { rel: 'alternate', hreflang: 'es', href: esUrl }]) + head.push(['link', { rel: 'alternate', hreflang: 'ar', href: arUrl }]) } else if (pageData.relativePath.startsWith('es/q/')) { const enUrl = `${siteUrl}/${path.replace('es/', '')}` + const arUrl = `${siteUrl}/ar/${path.replace('es/', '')}` head.push([ 'link', { rel: 'alternate', hreflang: 'es', href: canonicalUrl } ]) head.push(['link', { rel: 'alternate', hreflang: 'en', href: enUrl }]) + head.push(['link', { rel: 'alternate', hreflang: 'ar', href: arUrl }]) + } else if (pageData.relativePath.startsWith('ar/q/')) { + const enUrl = `${siteUrl}/${path.replace('ar/', '')}` + const esUrl = `${siteUrl}/es/${path.replace('ar/', '')}` + head.push([ + 'link', + { rel: 'alternate', hreflang: 'ar', href: canonicalUrl } + ]) + head.push(['link', { rel: 'alternate', hreflang: 'en', href: enUrl }]) + head.push(['link', { rel: 'alternate', hreflang: 'es', href: esUrl }]) } if (isQuestion && pageData.frontmatter.title) { @@ -136,9 +150,9 @@ export default defineConfig({ details: ` 170 questions covering Vue 3, Composition API, reactivity, components, TypeScript, testing, Nuxt, SSR, performance, architecture, and common interview scenarios. -Available in English and Spanish. +Available in English, Spanish, and Arabic. `.trim(), - ignoreFiles: ['index.md', 'es/index.md'] + ignoreFiles: ['index.md', 'es/index.md', 'ar/index.md'] }) ] }, @@ -206,6 +220,66 @@ Available in English and Spanish. code: '404' } } + }, + ar: { + label: 'العربية', + lang: 'ar', + dir: 'rtl', + description: + 'أسئلة مقابلات Vue.js مع إجابات وأمثلة على الكود', + themeConfig: { + returnToTopLabel: 'العودة إلى الأعلى', + sidebarMenuLabel: 'القائمة', + darkModeSwitchLabel: 'المظهر', + lightModeSwitchTitle: 'التبديل إلى المظهر الفاتح', + darkModeSwitchTitle: 'التبديل إلى المظهر الداكن', + outline: { + label: 'في هذه الصفحة' + }, + docFooter: { + prev: 'الصفحة السابقة', + next: 'الصفحة التالية' + }, + lastUpdated: { + text: 'آخر تحديث' + }, + editLink: { + pattern: + 'https://github.com/miguelfernandezdev/vuewiki.dev/edit/main/docs/:path', + text: 'اقترح تغييرات على هذه الصفحة' + }, + footer: { + message: 'منشور تحت رخصة MIT.', + copyright: 'حقوق النشر © 2026-الحالي Miguel Fernández Carratalá' + }, + nav: [ + { text: 'الأسئلة', link: '/ar/', activeMatch: '^/ar/$' }, + { text: 'حسب الصعوبة', link: '/ar/questions' }, + { text: 'بطاقات التعلم', link: '/ar/flashcards' }, + { text: 'الموارد', link: '/ar/resources' }, + { + text: 'روابط', + items: [ + { + text: 'المساهمة', + link: 'https://github.com/miguelfernandezdev/vuewiki.dev/blob/main/CONTRIBUTING.md' + }, + { + text: 'سجل التغييرات', + link: 'https://github.com/miguelfernandezdev/vuewiki.dev/commits/main' + } + ] + } + ], + notFound: { + title: 'الصفحة غير موجودة', + quote: + 'ولكن إذا لم تغير اتجاهك وواصلت البحث، فقد ينتهي بك الأمر حيث لا تريد الذهاب.', + linkLabel: 'اذهب إلى البداية', + linkText: 'خذني إلى البداية', + code: '404' + } + } } }, @@ -233,6 +307,25 @@ Available in English and Spanish. } } } + }, + ar: { + translations: { + button: { + buttonText: 'بحث', + buttonAriaLabel: 'بحث' + }, + modal: { + displayDetails: 'عرض القائمة المفصلة', + resetButtonTitle: 'مسح البحث', + backButtonTitle: 'إغلاق البحث', + noResultsText: 'لا توجد نتائج لـ', + footer: { + selectText: 'للاختيار', + navigateText: 'للتنقل', + closeText: 'للإغلاق' + } + } + } } } } diff --git a/docs/.vitepress/json-ld.ts b/docs/.vitepress/json-ld.ts index 5ceebcf..4df03c4 100644 --- a/docs/.vitepress/json-ld.ts +++ b/docs/.vitepress/json-ld.ts @@ -15,7 +15,8 @@ interface QuestionMeta { export function buildJsonLdHeads(meta: QuestionMeta): HeadConfig[] { const heads: HeadConfig[] = [] const isEs = meta.relativePath.startsWith('es/') - const lang = isEs ? 'es' : 'en' + const isAr = meta.relativePath.startsWith('ar/') + const lang = isEs ? 'es' : isAr ? 'ar' : 'en' const slug = meta.relativePath.replace(/\.md$/, '').replaceAll('/', '-') const ogImageUrl = meta.difficulty ? `${siteUrl}/og-${slug}.png` @@ -47,8 +48,8 @@ export function buildJsonLdHeads(meta: QuestionMeta): HeadConfig[] { article.dateModified = new Date(meta.lastUpdated).toISOString() } - const homeName = isEs ? 'Inicio' : 'Home' - const homeUrl = isEs ? `${siteUrl}/es/` : `${siteUrl}/` + const homeName = isEs ? 'Inicio' : isAr ? 'الرئيسية' : 'Home' + const homeUrl = isEs ? `${siteUrl}/es/` : isAr ? `${siteUrl}/ar/` : `${siteUrl}/` const breadcrumb = { '@context': 'https://schema.org', '@type': 'BreadcrumbList', diff --git a/docs/.vitepress/og-image.ts b/docs/.vitepress/og-image.ts index f680c09..0bb1a01 100644 --- a/docs/.vitepress/og-image.ts +++ b/docs/.vitepress/og-image.ts @@ -52,6 +52,40 @@ const TAG_ES: Record = { tooling: 'herramientas' } +const DIFFICULTY_AR: Record = { + beginner: 'مبتدئ', + intermediate: 'متوسط', + advanced: 'متقدم' +} + +const BADGE_WIDTHS_AR: Record = { + beginner: 130, + intermediate: 150, + advanced: 140 +} + +const TAG_AR: Record = { + accessibility: 'إمكانية الوصول', + animation: 'رسوم متحركة', + architecture: 'بنية معمارية', + components: 'مكونات', + core: 'أساسيات', + 'data-fetching': 'جلب البيانات', + debugging: 'تصحيح الأخطاء', + directives: 'توجيهات', + 'error-handling': 'معالجة الأخطاء', + errors: 'أخطاء', + forms: 'نماذج', + lifecycle: 'دورة الحياة', + migration: 'ترحيل', + performance: 'أداء', + reactivity: 'تفاعلية', + security: 'أمان', + 'state-management': 'إدارة الحالة', + styling: 'تنسيق', + tooling: 'أدوات' +} + function escapeXml(str: string): string { return str .replace(/&/g, '&') @@ -97,15 +131,20 @@ export async function generateOgImage( } const isEs = lang === 'es' + const isAr = lang === 'ar' const lines = wordWrap(title, 38) const badgeColor = BADGE_COLORS[difficulty] || BADGE_COLORS.beginner const badgeWidth = isEs ? BADGE_WIDTHS_ES[difficulty] || 180 - : BADGE_WIDTHS[difficulty] || 130 + : isAr + ? BADGE_WIDTHS_AR[difficulty] || 130 + : BADGE_WIDTHS[difficulty] || 130 const diffLabel = isEs ? DIFFICULTY_ES[difficulty] || difficulty.toUpperCase() - : difficulty.toUpperCase() - const translatedTags = isEs ? tags.map((t) => TAG_ES[t] || t) : tags + : isAr + ? DIFFICULTY_AR[difficulty] || difficulty.toUpperCase() + : difficulty.toUpperCase() + const translatedTags = isEs ? tags.map((t) => TAG_ES[t] || t) : isAr ? tags.map((t) => TAG_AR[t] || t) : tags const tagsText = translatedTags.join(' · ') const data: Record = { diff --git a/docs/.vitepress/sidebar.ts b/docs/.vitepress/sidebar.ts index 0d3c89c..19210cf 100644 --- a/docs/.vitepress/sidebar.ts +++ b/docs/.vitepress/sidebar.ts @@ -128,6 +128,46 @@ const TAG_LABELS_ES: Record = { teleport: 'Teleport' } +const TAG_LABELS_AR: Record = { + core: 'الأساسيات', + 'composition-api': 'Composition API', + reactivity: 'التفاعلية', + directives: 'التوجيهات', + components: 'المكونات', + lifecycle: 'دورة الحياة', + 'state-management': 'إدارة الحالة', + 'vue-router': 'Vue Router', + composables: 'Composables', + typescript: 'TypeScript', + testing: 'الاختبار', + performance: 'الأداء', + architecture: 'البنية المعمارية', + nuxt: 'Nuxt', + ssr: 'SSR', + errors: 'الأخطاء الشائعة', + 'error-handling': 'معالجة الأخطاء', + tooling: 'الأدوات', + forms: 'النماذج', + animation: 'الرسوم المتحركة', + styling: 'التنسيق', + migration: 'الترحيل', + 'data-fetching': 'جلب البيانات', + debugging: 'تصحيح الأخطاء', + security: 'الأمان', + accessibility: 'إمكانية الوصول', + pinia: 'Pinia', + vite: 'Vite', + vitest: 'Vitest', + vueuse: 'VueUse', + vuex: 'Vuex', + watchers: 'المراقبون', + 'v-model': 'v-model', + 'provide-inject': 'Provide/Inject', + slots: 'Slots', + suspense: 'Suspense', + teleport: 'Teleport' +} + function parseFrontmatter(content: string): QuestionFrontmatter | null { const match = content.match(/^---\n([\s\S]*?)\n---/) if (!match) return null @@ -171,7 +211,7 @@ function buildSidebarForLocale( }) } - const urlPrefix = subdir.startsWith('es/') ? '/es' : '' + const urlPrefix = subdir.startsWith('es/') ? '/es' : subdir.startsWith('ar/') ? '/ar' : '' return [...groups.entries()] .sort((a, b) => (TAG_ORDER[a[0]] ?? 99) - (TAG_ORDER[b[0]] ?? 99)) @@ -190,9 +230,11 @@ function buildSidebarForLocale( export function generateSidebar(docsDir: string): DefaultTheme.Sidebar { const en = buildSidebarForLocale(docsDir, 'q', TAG_LABELS_EN) const es = buildSidebarForLocale(docsDir, 'es/q', TAG_LABELS_ES) + const ar = buildSidebarForLocale(docsDir, 'ar/q', TAG_LABELS_AR) return { '/q/': en, - '/es/q/': es + '/es/q/': es, + '/ar/q/': ar } } diff --git a/docs/.vitepress/theme/QuestionMeta.vue b/docs/.vitepress/theme/QuestionMeta.vue index eb7b45c..7984569 100644 --- a/docs/.vitepress/theme/QuestionMeta.vue +++ b/docs/.vitepress/theme/QuestionMeta.vue @@ -11,7 +11,7 @@ const route = useRoute() const { isRead, markRead, toggleRead: _toggleRead } = useReadTracker() const isQuestion = computed(() => !!frontmatter.value.difficulty) -const backUrl = computed(() => (lang.value === 'es' ? '/es/' : '/')) +const backUrl = computed(() => lang.value === 'es' ? '/es/' : lang.value === 'ar' ? '/ar/' : '/') const questionUrl = computed(() => route.path) onMounted(() => { diff --git a/docs/.vitepress/theme/i18n.ts b/docs/.vitepress/theme/i18n.ts index f46ed28..c58c8e5 100644 --- a/docs/.vitepress/theme/i18n.ts +++ b/docs/.vitepress/theme/i18n.ts @@ -28,7 +28,7 @@ const messages: Record> = { hi } -export const completedLocales = new Set(['en', 'es']) +export const completedLocales = new Set(['en', 'es', 'ar']) export const translationIssues: Record = { zh: 7, @@ -38,7 +38,6 @@ export const translationIssues: Record = { fr: 11, de: 12, ru: 13, - ar: 14, vi: 15, hi: 16 } diff --git a/docs/.vitepress/theme/questions.data.ts b/docs/.vitepress/theme/questions.data.ts index fbf72d2..8e41feb 100644 --- a/docs/.vitepress/theme/questions.data.ts +++ b/docs/.vitepress/theme/questions.data.ts @@ -10,7 +10,7 @@ export interface QuestionData { summary: string } -export default createContentLoader('{,es/}q/*.md', { +export default createContentLoader('{,es/,ar/}q/*.md', { transform(rawData): QuestionData[] { return rawData .map((page) => ({ @@ -19,7 +19,7 @@ export default createContentLoader('{,es/}q/*.md', { difficulty: page.frontmatter.difficulty as string, tags: (page.frontmatter.tags || []) as string[], url: page.url, - locale: page.url.startsWith('/es/') ? 'es' : 'en', + locale: page.url.startsWith('/es/') ? 'es' : page.url.startsWith('/ar/') ? 'ar' : 'en', summary: (page.frontmatter.summary as string) ?? '' })) .sort((a, b) => a.order - b.order) as QuestionData[] diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css index f0a553a..7f9e6a1 100644 --- a/docs/.vitepress/theme/style.css +++ b/docs/.vitepress/theme/style.css @@ -77,3 +77,114 @@ .vp-doc h1:first-child { display: none; } + +/* RTL support for Arabic */ +[dir="rtl"] { + direction: rtl; + text-align: right; +} + +[dir="rtl"] .vp-doc table { + text-align: right; +} + +[dir="rtl"] .vp-doc blockquote { + border-left: none; + border-right: 2px solid var(--vp-c-divider); + padding-left: 0; + padding-right: 1rem; +} + +[dir="rtl"] .header-anchor { + left: auto; + right: 0; +} + +[dir="rtl"] .VPNav .title { + margin-left: 0; + margin-right: 0; +} + +[dir="rtl"] .VPHero .container .main { + text-align: right; +} + +[dir="rtl"] .VPFeatures .container .item { + text-align: right; +} + +[dir="rtl"] .search-wrapper .search-icon { + left: auto; + right: 1rem; +} + +[dir="rtl"] .search-wrapper .search-clear { + right: auto; + left: 0.75rem; +} + +[dir="rtl"] .search-input { + padding: 0.875rem 2.75rem 0.875rem 2.75rem; +} + +[dir="rtl"] .dropdown-panel { + left: auto; + right: 0; +} + +[dir="rtl"] .dropdown-option { + text-align: right; +} + +[dir="rtl"] .tag-count { + margin-left: 0; + margin-right: auto; +} + +[dir="rtl"] .back-link svg, +[dir="rtl"] .back-link .vp-icon { + transform: scaleX(-1); +} + +[dir="rtl"] .question-card { + border-left: 1px solid var(--vp-c-border); + border-right: 3px solid var(--vp-c-border); +} + +[dir="rtl"] .card-beginner { + border-left-color: var(--vp-c-border); + border-right-color: var(--vp-c-green-2); +} + +[dir="rtl"] .card-intermediate { + border-left-color: var(--vp-c-border); + border-right-color: var(--vp-c-yellow-2); +} + +[dir="rtl"] .card-advanced { + border-left-color: var(--vp-c-border); + border-right-color: var(--vp-c-red-2); +} + +[dir="rtl"] .card-beginner:hover { + border-left-color: var(--vp-c-border); + border-right-color: var(--vp-c-green-2); +} + +[dir="rtl"] .card-intermediate:hover { + border-left-color: var(--vp-c-border); + border-right-color: var(--vp-c-yellow-2); +} + +[dir="rtl"] .card-advanced:hover { + border-left-color: var(--vp-c-border); + border-right-color: var(--vp-c-red-2); +} + +[dir="rtl"] .filter-btn:first-child { + border-radius: 0 6px 6px 0; +} + +[dir="rtl"] .filter-btn:last-child { + border-radius: 6px 0 0 6px; +} diff --git a/docs/ar/flashcards.md b/docs/ar/flashcards.md new file mode 100644 index 0000000..01646f0 --- /dev/null +++ b/docs/ar/flashcards.md @@ -0,0 +1,6 @@ +--- +layout: page +title: بطاقات التعلم +--- + + diff --git a/docs/ar/index.md b/docs/ar/index.md new file mode 100644 index 0000000..4a8b378 --- /dev/null +++ b/docs/ar/index.md @@ -0,0 +1,34 @@ +--- +layout: home +title: VueWiki +titleTemplate: أسئلة مقابلات Vue.js مع إجابات وأمثلة تفاعلية + +hero: + name: VueWiki + text: أسئلة مقابلات Vue.js + tagline: استعد لمقابلتك القادمة في Vue.js مع أسئلة حقيقية وإجابات مفصلة وأمثلة برمجية. + image: + src: /logo.svg + alt: VueWiki + actions: + - theme: brand + text: تصفح الأسئلة + link: '#questions' + - theme: alt + text: GitHub + link: https://github.com/miguelfernandezdev/vuewiki.dev + +features: + - title: 📚 170+ سؤال + details: تغطي Vue 3، Composition API، التفاعلية، المكونات، TypeScript، Nuxt، SSR والمزيد. + - title: 🎯 من مبتدئ إلى متقدم + details: ثلاثة مستويات من الصعوبة لتدرس بشكل تدريجي أو تنتقل إلى الموضوعات التي تحتاجها. + - title: 🌍 الإنجليزية والإسبانية والعربية + details: محتوى متعدد اللغات كامل. بدّل بين اللغات دون أن تفقد مكانك. + - title: 💻 أمثلة برمجية + details: كل إجابة تتضمن مقتطفات برمجية عملية يمكنك استخدامها في مشاريع حقيقية. +--- + +
+ +
diff --git a/docs/ar/q/abort-controller-composable.md b/docs/ar/q/abort-controller-composable.md new file mode 100644 index 0000000..65d617c --- /dev/null +++ b/docs/ar/q/abort-controller-composable.md @@ -0,0 +1,234 @@ +--- +order: 94 +title: 'كيف تلغي طلب API في composable؟' +difficulty: 'intermediate' +tags: ['composables', 'vueuse', 'watchers', 'v-model'] +summary: 'أنشئ AbortController، مرر signal الخاص به إلى fetch، واستدع abort() عند إلغاء التثبيت أو عندما يحل طلب جديد محل طلب قديم.' +--- + +استخدم واجهة برمجة `AbortController` في المتصفح. أنشئ متحكمًا، مرر `signal` الخاص به إلى `fetch`، واستدع `controller.abort()` عندما تحتاج إلى الإلغاء. في Vue، المشغلان الأكثر شيوعًا للإلغاء هما إلغاء تثبيت المكون (منع تحديثات الحالة على المكونات المدمرة) والطلبات الجديدة التي تحل محل الطلبات القديمة (منع حالات السباق). + +## أساسيات AbortController + +```js +const controller = new AbortController() + +fetch('/api/users', { signal: controller.signal }) + .then((res) => res.json()) + .then((data) => console.log(data)) + .catch((err) => { + if (err.name === 'AbortError') { + console.log('Request was cancelled') + } + }) + +// Cancel the request +controller.abort() +``` + +استدعاء `abort()` يرفض وعد fetch مع خطأ `AbortError`. تتحقق من `err.name` لتمييز الإلغاء عن الأخطاء الحقيقية. + +مخطط تسلسلي يوضح AbortController وهو يلغي طلبًا قديمًا عند بدء طلب جديد + +## الإلغاء عند إلغاء التثبيت + +عندما يتم إلغاء تثبيت مكون أثناء وجود طلب قيد التنفيذ، تصل الاستجابة بعد تدمير المكون. تعيين حالة على مكون مدمر يسبب تسرب ذاكرة وقد ينتج تحذيرات. + +```ts +// composables/useFetch.ts +export function useFetch(url: MaybeRefOrGetter) { + const data = ref(null) + const error = ref(null) + const isLoading = ref(false) + + let controller: AbortController | null = null + + async function execute() { + controller?.abort() + controller = new AbortController() + + isLoading.value = true + error.value = null + + try { + const response = await fetch(toValue(url), { + signal: controller.signal + }) + data.value = await response.json() + } catch (e) { + if ((e as Error).name !== 'AbortError') { + error.value = e as Error + } + } finally { + isLoading.value = false + } + } + + watch(() => toValue(url), execute, { immediate: true }) + + onUnmounted(() => controller?.abort()) + + return { data, error, isLoading, execute } +} +``` + +```vue + +``` + + + +يقوم خطاف `onUnmounted` بإلغاء أي طلب قيد التنفيذ. يضمن التقاط `AbortError` بقاء حالة الخطأ نظيفة. + +## إلغاء الطلبات القديمة (حالة السباق) + +عندما يتغير عنوان URL بسرعة (البحث أثناء الكتابة)، يمكن أن تكون طلبات متعددة قيد التنفيذ في نفس الوقت. بدون إلغاء، قد تصل النتائج بترتيب غير صحيح: + +``` +User types: "v" → "vu" → "vue" +Request 1: /api/search?q=v (sent first) +Request 2: /api/search?q=vu (sent second) +Request 3: /api/search?q=vue (sent third) + +Response order: Request 2, Request 3, Request 1 +Result displayed: "v" results (wrong!) +``` + +المكون composable أعلاه يعالج هذا بالفعل. كل استدعاء لـ `execute` يلغي المتحكم السابق قبل إنشاء متحكم جديد: + +```vue + + + +``` + + + +عندما يتغير `query` من "vu" إلى "vue"، يقوم المراقب بتشغيل `execute`، الذي يلغي طلب "vu" ويبدأ طلب "vue". فقط النتيجة الأخيرة تصل. + +## مع axios + +يدعم Axios `AbortController` بنفس الطريقة: + +```ts +import axios from 'axios' + +export function useFetch(url: MaybeRefOrGetter) { + const data = ref(null) + let controller: AbortController | null = null + + async function execute() { + controller?.abort() + controller = new AbortController() + + try { + const response = await axios.get(toValue(url), { + signal: controller.signal + }) + data.value = response.data + } catch (e) { + if (!axios.isCancel(e)) { + // handle real errors + } + } + } + + watch(() => toValue(url), execute, { immediate: true }) + onUnmounted(() => controller?.abort()) + + return { data, execute } +} +``` + +يوفر Axios `axios.isCancel(e)` كفحص أنظف من مقارنة `error.name`. + +## المهلة مع AbortSignal.timeout + +للطلبات التي يجب أن تفشل بعد حد زمني، استخدم `AbortSignal.timeout` (متاح في المتصفحات الحديثة): + +```ts +async function execute() { + controller?.abort() + controller = new AbortController() + + const timeoutSignal = AbortSignal.timeout(5000) + const combinedSignal = AbortSignal.any([controller.signal, timeoutSignal]) + + const response = await fetch(toValue(url), { + signal: combinedSignal + }) + // ... +} +``` + +`AbortSignal.any` يجمع إشارات متعددة. يتم إلغاء الطلب إما عند إلغاء تثبيت المكون (إلغاء يدوي) أو بعد مرور 5 ثوانٍ (مهلة). + +## الإلغاء المدمج في Nuxt + +يتولى `useFetch` و `useAsyncData` في Nuxt الإلغاء تلقائيًا. عند إلغاء تثبيت المكون أو تغيير المعلمات المراقبة، يلغي Nuxt الطلب السابق: + +```vue + +``` + + + +لا حاجة لـ `AbortController` يدوي. هذا أحد أسباب تفضيل جلب بيانات Nuxt على `fetch` الخام في تطبيقات Nuxt. + +## متى تلغي + +| السيناريو | لماذا الإلغاء | +| -------------------- | ------------------------------------------------- | +| إلغاء تثبيت المكون | منع تحديثات الحالة على مكون مدمر | +| تغيير إدخال البحث | منع النتائج القديمة من استبدال النتائج الجديدة | +| التنقل بين المسارات | التوقف عن جلب بيانات لصفحة غادرها المستخدم | +| المهلة | الفشل بسرعة بدلاً من الانتظار إلى أجل غير مسمى | +| نقرة المستخدم على "إلغاء" | احترام نية المستخدم الصريحة | + +انظر أيضًا: [كيف تبني composable لجلب البيانات؟](/ar/q/composable-data-fetching) · [ما هو composable؟](/ar/q/what-is-a-composable) · [كيف تتعامل composables غير المتزامنة مع الأخطاء وحالة التحميل؟](/ar/q/async-composable-error-handling) + +## المراجع + +- [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) - MDN +- [AbortSignal.timeout()](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static) - MDN +- [Composables](https://vuejs.org/guide/reusability/composables.html) - Vue.js docs +- [Data Fetching](https://nuxt.com/docs/getting-started/data-fetching) - Nuxt docs diff --git a/docs/ar/q/accessibility-in-vue.md b/docs/ar/q/accessibility-in-vue.md new file mode 100644 index 0000000..eebd726 --- /dev/null +++ b/docs/ar/q/accessibility-in-vue.md @@ -0,0 +1,270 @@ +--- +order: 169 +title: 'كيف تتعامل مع إمكانية الوصول في Vue؟' +difficulty: 'intermediate' +tags: ['accessibility', 'components', 'teleport'] +summary: 'استخدم HTML الدلالي، أضف ARIA فقط عندما لا تكون الدلالات الأصلية كافية، أدر التركيز عند تغيير المسارات، واختبر باستخدام قارئات الشاشة.' +--- + +ابدأ بعناصر HTML الدلالية بدلاً من divs. أضف سمات ARIA فقط عندما لا تكون الدلالات الأصلية كافية. أدر التركيز بشكل صريح عند تغيير المسارات والمحتوى الديناميكي. Vue لا يضيف أي عوائق لإمكانية الوصول، لكنه لا يضيف أي حواجز حماية أيضًا. الأمر متروك لك. + +## HTML الدلالي أولاً + +أهم قرار يؤثر على إمكانية الوصول لا علاقة له بـ Vue: + +```vue + + + + + +``` + + + +العناصر الأصلية تمنحك دعم لوحة المفاتيح وإعلانات قارئ الشاشة وسلوك التركيز مجانًا. ARIA لا يمكنها إضافة وظائف، يمكنها فقط وصف ما هو موجود بالفعل. + +## سمات ARIA في قوالب Vue + +اربط سمات ARIA بشكل ديناميكي عندما تتحكم الحالة في واجهة المستخدم: + +```vue + + + +``` + + + +`useId()` (Vue 3.5+) يُنشئ معرفًا فريدًا لكل نسخة من المكون، متجنبًا المعرفات المكررة عند إعادة استخدام المكون. + +## إدارة التركيز + +تطبيقات الصفحة الواحدة تكسر سلوك المتصفح الافتراضي حيث ينقل التنقل بين الصفحات التركيز إلى أعلى الصفحة الجديدة. تحتاج إلى معالجة هذا يدويًا: + +```ts +// router/index.ts +router.afterEach((to, from) => { + if (to.path !== from.path) { + nextTick(() => { + const heading = document.querySelector('h1') + if (heading instanceof HTMLElement) { + heading.setAttribute('tabindex', '-1') + heading.focus() + } + }) + } +}) +``` + +للنوافذ المنبثقة ومربعات الحوار، احصر التركيز داخل العنصر وأعده عند إغلاق النافذة المنبثقة: + +```vue + + + +``` + + + +استخدام عنصر `` الأصلي يتعامل مع حصر التركيز تلقائيًا عند فتحه بـ `showModal()`. + +## المناطق الحية للمحتوى الديناميكي + +عندما يتم تحديث المحتوى دون إعادة تحميل الصفحة، لن تعلن قارئات الشاشة عنه ما لم تستخدم منطقة حية: + +```vue + + + +``` + + + +`aria-live="polite"` تنتظر حتى ينهي قارئ الشاشة إعلانه الحالي. استخدم `aria-live="assertive"` فقط للرسائل العاجلة مثل الأخطاء. + +## مخفي بصريًا ولكن متاح + +محتوى يجب أن يكون متاحًا لقارئات الشاشة ولكن غير مرئي على الشاشة: + +```css +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} +``` + +```vue + +``` + + + +بدون النص المخفي بصريًا، سيعلن قارئ الشاشة فقط "زر" دون أي إشارة إلى وظيفته. + +## قائمة التحقق + +| المجال | ما يجب فعله | +| -------------- | ----------------------------------------------------------------------------- | +| HTML الدلالي | استخدم `nav`، `main`، `button`، `ul`، `dialog` بدلاً من divs | +| ARIA | أضف فقط عندما لا تكون الدلالات الأصلية كافية. اربط ديناميكيًا بـ `:aria-*` | +| التركيز | أدر عند تغيير المسارات والنوافذ المنبثقة والمحتوى الديناميكي | +| المناطق الحية | أعلن تغييرات المحتوى الديناميكي باستخدام `aria-live` | +| لوحة المفاتيح | تأكد من أن جميع العناصر التفاعلية قابلة للوصول والتشغيل بلوحة المفاتيح | +| تباين الألوان | الحد الأدنى 4.5:1 للنص، 3:1 للنص الكبير (WCAG AA) | +| التسميات | كل إدخال نموذج يحتاج إلى `