diff --git a/.github/workflows/lunaria.yml b/.github/workflows/lunaria.yml index f951b3afb88d8..643bcbf9c481d 100644 --- a/.github/workflows/lunaria.yml +++ b/.github/workflows/lunaria.yml @@ -33,6 +33,6 @@ jobs: uses: ./.github/actions/install - name: Generate Lunaria Overview - uses: lunariajs/action@4911ad0736d1e3b20af4cb70f5079aea2327ed8e # astro-docs + uses: lunariajs/action@e5ab09c3c8353fa80786d8c2149873ba3978a3a7 # v0.2.0 with: token: ${{ secrets.FREDKBOT_GITHUB_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a661afd021a08..efcd78b0134e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -160,7 +160,7 @@ git checkout -b add/partial-hydration-typo-fix ``` ### Opening a PR -One you have made your changes using any of the above methods, you’re ready to create a “Pull Request!” +Once you have made your changes using any of the above methods, you’re ready to create a “Pull Request!” This will let the Astro docs team know you have some changes to propose. At this point we can give you feedback and might request changes. For translations, we like to have at least one other person who knows the language you are translating into review the PR. diff --git a/lunaria.config.ts b/lunaria.config.ts index aaddd51d5dfa2..17741d053b718 100644 --- a/lunaria.config.ts +++ b/lunaria.config.ts @@ -1,3 +1,4 @@ +import { html } from '@lunariajs/core'; import { defineConfig } from '@lunariajs/core/config'; export default defineConfig({ @@ -132,4 +133,43 @@ export default defineConfig({ 'i18nIgnore', ], }, + dashboard: { + title: 'Astro Docs Translation Status', + description: + 'Translation progress tracker for the Astro Docs site. See how much has been translated in your language and get involved!', + site: 'https://i18n.docs.astro.build/', + basesToHide: ['src/content/docs/en/', 'src/i18n/en/', 'src/content/docs/', 'src/content/'], + customCss: ['./scripts/lunaria/styles.css'], + favicon: { + external: [ + { link: 'https://docs.astro.build/favicon.ico', type: 'image/x-icon' }, + { link: 'https://docs.astro.build/favicon.svg', type: 'image/svg+xml' }, + ], + }, + ui: { + 'statusByLocale.heading': 'Translation progress by locale', + 'statusByLocale.outdatedLocalizationLink': 'outdated translation', + 'statusByLocale.incompleteLocalizationLink': 'incomplete translation', + 'statusByLocale.completeLocalization': 'This translation is complete, amazing job! 🎉', + 'statusByFile.heading': 'Translation status by file', + }, + }, + renderer: { + slots: { + head: () => html``, + afterTitle: () => html` +

+ If you're interested in helping us translate + docs.astro.build into one of the languages listed + below, you've come to the right place! This auto-updating page always lists all the + content that could use your help right now. +

+

+ Before starting, please read our + i18n Guide + to learn about our translation process and how you can get involved. +

+ `, + }, + }, }); diff --git a/package.json b/package.json index ec2d650f0790f..7cfdc1e610e2e 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@astrojs/sitemap": "^3.7.3", "@astrojs/starlight": "^0.42.0", "@expressive-code/plugin-collapsible-sections": "^0.44.1", - "@lunariajs/core": "https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@722c34c", + "@lunariajs/core": "^0.2.0", "canvas-confetti": "^1.6.0", "jsdoc-api": "^9.3.5", "satteri": "^0.10.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23891b363ad0f..0e6cac730c021 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,13 +19,13 @@ importers: version: 3.7.3 '@astrojs/starlight': specifier: ^0.42.0 - version: 0.42.0(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3))(typescript@6.0.3) + version: 0.42.0(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1))(typescript@6.0.3) '@expressive-code/plugin-collapsible-sections': specifier: ^0.44.1 version: 0.44.1 '@lunariajs/core': - specifier: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@722c34c - version: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@722c34c + specifier: ^0.2.0 + version: 0.2.0 canvas-confetti: specifier: ^1.6.0 version: 1.6.0 @@ -62,16 +62,16 @@ importers: version: 24.12.2 '@typescript-eslint/parser': specifier: ^8.59.3 - version: 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) + version: 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) astro: specifier: ^7.2.10 - version: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3) + version: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1) astro-eslint-parser: specifier: ^1.4.0 version: 1.4.0 astro-og-canvas: specifier: ^0.11.1 - version: 0.11.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3)) + version: 0.11.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1)) canvaskit-wasm: specifier: ^0.41.1 version: 0.41.1 @@ -83,10 +83,10 @@ importers: version: 5.0.3 eslint: specifier: ^10.3.0 - version: 10.3.0(jiti@2.3.3) + version: 10.3.0(jiti@2.7.0) eslint-plugin-astro: specifier: ^1.6.0 - version: 1.7.0(eslint@10.3.0(jiti@2.3.3)) + version: 1.7.0(eslint@10.3.0(jiti@2.7.0)) fast-glob: specifier: ^3.3.3 version: 3.3.3 @@ -122,7 +122,7 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.59.3 - version: 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) + version: 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) wrangler: specifier: ^4.99.0 version: 4.99.0 @@ -1269,10 +1269,10 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} - '@lunariajs/core@https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@722c34c': - resolution: {tarball: https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@722c34c} - version: 0.1.1 + '@lunariajs/core@0.2.0': + resolution: {integrity: sha512-JuQsDStxiznw4Km0ab/FNH2VxVfWm08oJdjevRUOY7duN6nPKvBkk9rvXD6zPDlvauPWTf9RFuJ3lD4l+o9M4g==} engines: {node: '>=18.17.0'} + hasBin: true '@napi-rs/wasm-runtime@1.2.3': resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} @@ -1483,6 +1483,12 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@simple-git/args-pathspec@1.0.3': + resolution: {integrity: sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==} + + '@simple-git/argv-parser@1.1.1': + resolution: {integrity: sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==} + '@sindresorhus/is@7.2.0': resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} @@ -1962,6 +1968,10 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + cookie-es@1.2.3: resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} @@ -2112,6 +2122,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -2386,6 +2399,10 @@ packages: resolution: {integrity: sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==} engines: {node: '>=20.20.0'} + gettext-parser@9.1.1: + resolution: {integrity: sha512-ZLeqWPz9OMNrTgMuww0C22kkcNqis+e4059R94t7L7ERlZ2rUNpiDbaAUus+esBC6uBAQWbS9N+R5vJIJg//lw==} + engines: {node: '>=20'} + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -2502,6 +2519,10 @@ packages: typescript: optional: true + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2596,8 +2617,8 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jiti@2.3.3: - resolution: {integrity: sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true js-yaml@3.14.1: @@ -3089,8 +3110,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - p-all@5.0.0: - resolution: {integrity: sha512-pofqu/1FhCVa+78xNAptCGc9V45exFz2pvBRyIvgXkNM0Rh18Py7j8pQuSjA+zpabI46v9hRjNWmL9EAFcEbpw==} + p-all@5.0.1: + resolution: {integrity: sha512-LMT7WX9ZSaq3J1zjloApkIVmtz0ZdMFSIqbuiEa3txGYPLjUPOvgOPOx3nFjo+f37ZYL+1aY666I2SG7GVwLOA==} engines: {node: '>=16'} p-limit@1.3.0: @@ -3206,6 +3227,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -3384,6 +3409,9 @@ packages: s.color@0.0.15: resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass-formatter@0.7.6: resolution: {integrity: sha512-hXdxU6PCkiV3XAiSnX+XLqz2ohHoEnVUlrd8LEVMAI80uB1+OTScIkH9n6qQwImZpTye1r1WG1rbGUteHNhoHg==} @@ -3443,8 +3471,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-git@3.27.0: - resolution: {integrity: sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==} + simple-git@3.36.0: + resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==} sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -4046,6 +4074,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@22.0.0: resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} @@ -4068,12 +4101,12 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.6.2: + resolution: {integrity: sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ==} + zwitch@1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} @@ -4335,11 +4368,11 @@ snapshots: github-slugger: 2.0.0 satteri: 0.10.5 - '@astrojs/mdx@8.0.0(@astrojs/markdown-satteri@0.4.0)(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3))': + '@astrojs/mdx@8.0.0(@astrojs/markdown-satteri@0.4.0)(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@astrojs/internal-helpers': 0.11.0 '@astrojs/markdown-satteri': 0.4.0 - astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3) + astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1) es-module-lexer: 2.0.0 '@astrojs/prism@4.0.2': @@ -4352,17 +4385,17 @@ snapshots: stream-replace-string: 2.0.0 zod: 4.3.6 - '@astrojs/starlight@0.42.0(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3))(typescript@6.0.3)': + '@astrojs/starlight@0.42.0(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1))(typescript@6.0.3)': dependencies: '@astrojs/markdown-satteri': 0.4.0 - '@astrojs/mdx': 8.0.0(@astrojs/markdown-satteri@0.4.0)(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3)) + '@astrojs/mdx': 8.0.0(@astrojs/markdown-satteri@0.4.0)(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1)) '@astrojs/sitemap': 3.7.3 '@pagefind/default-ui': 1.3.0 '@types/hast': 3.0.5 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3) - astro-expressive-code: 0.44.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3)) + astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1) + astro-expressive-code: 0.44.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1)) bcp-47: 2.1.0 hast-util-format: 1.1.0 hast-util-select: 6.0.4 @@ -4732,9 +4765,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0(jiti@2.3.3))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0(jiti@2.7.0))': dependencies: - eslint: 10.3.0(jiti@2.3.3) + eslint: 10.3.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -5040,19 +5073,20 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} - '@lunariajs/core@https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@722c34c': + '@lunariajs/core@0.2.0': dependencies: consola: 3.4.2 - jiti: 2.3.3 - js-yaml: 4.3.1 - neotraverse: 0.6.18 - p-all: 5.0.0 + gettext-parser: 9.1.1 + jiti: 2.7.0 + neotraverse: 1.0.1 + p-all: 5.0.1 path-to-regexp: 6.3.0 - picomatch: 4.0.4 - simple-git: 3.27.0 + picomatch: 4.0.7 + simple-git: 3.36.0 tinyglobby: 0.2.17 ultramatter: 0.0.4 - zod: 3.25.76 + yaml: 2.9.1 + zod: 4.6.2 transitivePeerDependencies: - supports-color @@ -5224,6 +5258,12 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@simple-git/args-pathspec@1.0.3': {} + + '@simple-git/argv-parser@1.1.1': + dependencies: + '@simple-git/args-pathspec': 1.0.3 + '@sindresorhus/is@7.2.0': {} '@speed-highlight/core@1.2.16': {} @@ -5422,15 +5462,15 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3))(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.59.3 - '@typescript-eslint/type-utils': 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.59.3 - eslint: 10.3.0(jiti@2.3.3) + eslint: 10.3.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -5438,14 +5478,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.59.3 '@typescript-eslint/types': 8.59.3 '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.59.3 debug: 4.4.3 - eslint: 10.3.0(jiti@2.3.3) + eslint: 10.3.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -5468,13 +5508,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.59.3 '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.3.0(jiti@2.3.3) + eslint: 10.3.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -5497,13 +5537,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.3.3)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.59.3 '@typescript-eslint/types': 8.59.3 '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) - eslint: 10.3.0(jiti@2.3.3) + eslint: 10.3.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -5659,20 +5699,20 @@ snapshots: transitivePeerDependencies: - supports-color - astro-expressive-code@0.44.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3)): + astro-expressive-code@0.44.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1)): dependencies: - astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3) + astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1) rehype-expressive-code: 0.44.1 url-extras: 0.1.0 - astro-og-canvas@0.11.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3)): + astro-og-canvas@0.11.1(astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1)): dependencies: - astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3) + astro: 7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1) canvaskit-wasm: 0.41.1 deterministic-object-hash: 2.0.2 entities: 8.0.0 - astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.3.3)(yaml@2.8.3): + astro@7.2.10(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.1): dependencies: '@astrojs/compiler-rs': 0.4.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3) '@astrojs/internal-helpers': 0.11.0 @@ -5722,8 +5762,8 @@ snapshots: ultrahtml: 1.6.0 unifont: 0.7.5 unstorage: 1.17.5 - vite: 8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.3.3)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.3.3)(yaml@2.8.3)) + vite: 8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1) + vitefu: 1.1.2(vite@8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1)) xxhash-wasm: 1.1.0 yargs-parser: 22.0.0 zod: 4.3.6 @@ -5903,6 +5943,8 @@ snapshots: consola@3.4.2: {} + content-type@1.0.5: {} + cookie-es@1.2.3: {} cookie@1.1.1: {} @@ -6038,6 +6080,10 @@ snapshots: emoji-regex@9.2.2: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + entities@4.5.0: {} entities@6.0.0: {} @@ -6126,19 +6172,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.6.4(eslint@10.3.0(jiti@2.3.3)): + eslint-compat-utils@0.6.4(eslint@10.3.0(jiti@2.7.0)): dependencies: - eslint: 10.3.0(jiti@2.3.3) + eslint: 10.3.0(jiti@2.7.0) semver: 7.8.5 - eslint-plugin-astro@1.7.0(eslint@10.3.0(jiti@2.3.3)): + eslint-plugin-astro@1.7.0(eslint@10.3.0(jiti@2.7.0)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.3.3)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.7.0)) '@jridgewell/sourcemap-codec': 1.5.5 '@typescript-eslint/types': 8.59.3 astro-eslint-parser: 1.4.0 - eslint: 10.3.0(jiti@2.3.3) - eslint-compat-utils: 0.6.4(eslint@10.3.0(jiti@2.3.3)) + eslint: 10.3.0(jiti@2.7.0) + eslint-compat-utils: 0.6.4(eslint@10.3.0(jiti@2.7.0)) globals: 16.5.0 postcss: 8.5.15 postcss-selector-parser: 7.0.0 @@ -6163,9 +6209,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.3.0(jiti@2.3.3): + eslint@10.3.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.3.3)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.5.5 @@ -6196,7 +6242,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.3.3 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -6368,6 +6414,11 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + gettext-parser@9.1.1: + dependencies: + content-type: 1.0.5 + encoding: 0.1.13 + github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -6542,6 +6593,10 @@ snapshots: optionalDependencies: typescript: 6.0.3 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -6610,7 +6665,7 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jiti@2.3.3: {} + jiti@2.7.0: {} js-yaml@3.14.1: dependencies: @@ -7265,7 +7320,7 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - p-all@5.0.0: + p-all@5.0.1: dependencies: p-map: 6.0.0 @@ -7381,6 +7436,8 @@ snapshots: picomatch@4.0.4: {} + picomatch@4.0.7: {} + pify@2.3.0: {} pify@3.0.0: {} @@ -7584,6 +7641,8 @@ snapshots: s.color@0.0.15: {} + safer-buffer@2.1.2: {} + sass-formatter@0.7.6: dependencies: suf-log: 2.5.3 @@ -7712,10 +7771,12 @@ snapshots: signal-exit@4.1.0: {} - simple-git@3.27.0: + simple-git@3.36.0: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 + '@simple-git/args-pathspec': 1.0.3 + '@simple-git/argv-parser': 1.1.1 debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -7975,13 +8036,13 @@ snapshots: dependencies: semver: 7.8.5 - typescript-eslint@8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3): + typescript-eslint@8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3))(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(jiti@2.3.3))(typescript@6.0.3) - eslint: 10.3.0(jiti@2.3.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.3.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -8129,7 +8190,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.3.3)(yaml@2.8.3): + vite@8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -8140,12 +8201,12 @@ snapshots: '@types/node': 24.12.2 esbuild: 0.28.1 fsevents: 2.3.3 - jiti: 2.3.3 - yaml: 2.8.3 + jiti: 2.7.0 + yaml: 2.9.1 - vitefu@1.1.2(vite@8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.3.3)(yaml@2.8.3)): + vitefu@1.1.2(vite@8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1)): optionalDependencies: - vite: 8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.3.3)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.1) volar-service-css@0.0.70(@volar/language-service@2.4.28): dependencies: @@ -8322,6 +8383,8 @@ snapshots: yaml@2.8.3: {} + yaml@2.9.1: {} + yargs-parser@22.0.0: {} yargs@18.1.0: @@ -8350,10 +8413,10 @@ snapshots: cookie: 1.1.1 youch-core: 0.3.3 - zod@3.25.76: {} - zod@4.3.6: {} + zod@4.6.2: {} + zwitch@1.0.5: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3721b47ebc6a4..4b55438004fed 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -17,6 +17,7 @@ minimumReleaseAgeExclude: - '@astrojs/mdx@8.0.0' - '@astrojs/starlight@0.42.0' - '@astrojs/internal-helpers@0.11.0' + - '@lunariajs/core@0.2.0' # We don’t need to run these build scripts, so explicitly disallow them as a minor security measure. allowBuilds: diff --git a/public/logos/ishosting.svg b/public/logos/ishosting.svg new file mode 100644 index 0000000000000..90cf199e085d4 --- /dev/null +++ b/public/logos/ishosting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/lunaria.mts b/scripts/lunaria.mts index 394a3d0d9d1a7..66349446b4cf5 100644 --- a/scripts/lunaria.mts +++ b/scripts/lunaria.mts @@ -1,13 +1,12 @@ -import { createLunaria } from '@lunariajs/core'; +import { createLunaria, generateDashboard } from '@lunariajs/core'; import { mkdirSync, writeFileSync } from 'node:fs'; -import { Page, SvgSummary } from './lunaria/components.ts'; +import { join } from 'node:path'; +import { SvgSummary } from './lunaria/summary.ts'; const lunaria = await createLunaria(); const status = await lunaria.getFullStatus(); -const html = Page(lunaria.config, status, lunaria); -const svg = SvgSummary(lunaria.config, status); - -mkdirSync('dist/lunaria', { recursive: true }); -writeFileSync('dist/lunaria/index.html', html); -writeFileSync('dist/lunaria/summary.svg', svg); +const outDir = lunaria.config.outDir; +mkdirSync(outDir, { recursive: true }); +writeFileSync(join(outDir, 'index.html'), generateDashboard(lunaria.config, status)); +writeFileSync(join(outDir, 'summary.svg'), SvgSummary(lunaria.config, status)); diff --git a/scripts/lunaria/components.ts b/scripts/lunaria/components.ts deleted file mode 100644 index fed41a17bfc3c..0000000000000 --- a/scripts/lunaria/components.ts +++ /dev/null @@ -1,434 +0,0 @@ -import { - createLunaria, - type Locale, - type LunariaConfig, - type LunariaStatus, - type StatusEntry, -} from '@lunariajs/core'; -import { BaseStyles, CustomStyles } from './styles.ts'; - -export function html( - strings: TemplateStringsArray, - ...values: ((string | number) | (string | number)[])[] -) { - const treatedValues = values.map((value) => (Array.isArray(value) ? value.join('') : value)); - - return String.raw({ raw: strings }, ...treatedValues); -} - -type LunariaInstance = Awaited>; - -function collapsePath(path: string) { - const basesToHide = ['src/content/docs/en/', 'src/i18n/en/', 'src/content/docs/', 'src/content/']; - - for (const base of basesToHide) { - const newPath = path.replace(base, ''); - - if (newPath === path) continue; - return newPath; - } - - return path; -} - -export const Page = ( - config: LunariaConfig, - status: LunariaStatus, - lunaria: LunariaInstance -): string => { - return html` - - - - ${Meta} ${BaseStyles} ${CustomStyles} - - - ${Body(config, status, lunaria)} - - - `; -}; - -export const Meta = html` - - - Astro Docs Translation Status - - - - - - - - - -`; - -export const Body = ( - config: LunariaConfig, - status: LunariaStatus, - lunaria: LunariaInstance -): string => { - return html` -
-
-

Astro Docs Translation Status

- ${TitleParagraph} ${StatusByLocale(config, status, lunaria)} -
- ${StatusByFile(config, status, lunaria)} -
- `; -}; - -export const StatusByLocale = ( - config: LunariaConfig, - status: LunariaStatus, - lunaria: LunariaInstance -): string => { - const { locales } = config; - return html` -

- Translation progress by locale -

- ${locales.map((locale) => LocaleDetails(status, locale, lunaria))} - `; -}; - -export const LocaleDetails = ( - status: LunariaStatus, - locale: Locale, - lunaria: LunariaInstance -): string => { - const { label, lang } = locale; - - const missingFiles = status.filter( - (file) => - file.localizations.find((localization) => localization.lang === lang)?.status === 'missing' - ); - const outdatedFiles = status.filter((file) => { - const localization = file.localizations.find((localization) => localization.lang === lang); - - if (!localization || localization.status === 'missing') return false; - if (file.type === 'dictionary') - return 'missingKeys' in localization ? localization.missingKeys.length > 0 : false; - - return ( - localization.status === 'outdated' || - ('missingKeys' in localization && localization.missingKeys.length > 0) - ); - }); - - const doneLength = status.length - outdatedFiles.length - missingFiles.length; - - const links = lunaria.gitHostingLinks(); - - return html` -
- - ${label} (${lang}) -
- - ${doneLength.toString()} done, ${outdatedFiles.length.toString()} outdated, - ${missingFiles.length.toString()} missing - -
- ${ProgressBar(status.length, outdatedFiles.length, missingFiles.length)} -
- ${outdatedFiles.length > 0 ? OutdatedFiles(outdatedFiles, lang, lunaria) : ''} - ${missingFiles.length > 0 - ? html`

Missing

- ` - : ''} - ${missingFiles.length == 0 && outdatedFiles.length == 0 - ? html`

This translation is complete, amazing job! 🎉

` - : ''} -
- `; -}; - -export const OutdatedFiles = ( - outdatedFiles: LunariaStatus, - lang: string, - lunaria: LunariaInstance -): string => { - return html` -

Outdated

- - `; -}; - -export const StatusByFile = ( - config: LunariaConfig, - status: LunariaStatus, - lunaria: LunariaInstance -): string => { - const { locales } = config; - return html` -

- Translation status by file -

- - - - ${['File', ...locales.map(({ lang }) => lang)].map((col) => html``)} - - - ${TableBody(status, locales, lunaria)} -
${col}
- ❌ missing   🔄 outdated   ✔ done - `; -}; - -export const TableBody = ( - status: LunariaStatus, - locales: Locale[], - lunaria: LunariaInstance -): string => { - const links = lunaria.gitHostingLinks(); - - return html` - - ${status.map( - (file) => - html` - - ${Link(links.source(file.source.path), collapsePath(file.source.path))} - ${locales.map(({ lang }) => { - return TableContentStatus(file.localizations, lang, lunaria); - })} - - ` - )} - - `; -}; - -export const TableContentStatus = ( - localizations: StatusEntry['localizations'], - lang: string, - lunaria: LunariaInstance -): string => { - const localization = localizations.find((localization) => localization.lang === lang)!; - const isMissingKeys = 'missingKeys' in localization && localization.missingKeys.length > 0; - const status = isMissingKeys ? 'outdated' : localization.status; - const links = lunaria.gitHostingLinks(); - const link = - status === 'missing' ? links.create(localization.path) : links.source(localization.path); - return html`${EmojiFileLink(link, status)}`; -}; - -export const ContentDetailsLinks = ( - fileStatus: StatusEntry, - lang: string, - lunaria: LunariaInstance -): string => { - const localization = fileStatus.localizations.find((localization) => localization.lang === lang)!; - const isMissingKeys = - localization.status !== 'missing' && - 'missingKeys' in localization && - localization.missingKeys.length > 0; - - const links = lunaria.gitHostingLinks(); - - return html` - ${Link(links.source(fileStatus.source.path), collapsePath(fileStatus.source.path))} - (${Link( - links.source(localization.path), - isMissingKeys ? 'incomplete translation' : 'outdated translation' - )}, - ${Link( - links.history( - fileStatus.source.path, - 'git' in localization - ? new Date(localization.git.latestTrackedCommit.date).toISOString() - : undefined - ), - 'source change history' - )}) - `; -}; - -export const EmojiFileLink = ( - href: string | null, - type: 'missing' | 'outdated' | 'up-to-date' -): string => { - const statusTextOpts = { - missing: 'missing', - outdated: 'outdated', - 'up-to-date': 'done', - } as const; - - const statusEmojiOpts = { - missing: '❌', - outdated: '🔄', - 'up-to-date': '✔', - } as const; - - return href - ? html` - - ` - : html` - - `; -}; - -export const Link = (href: string, text: string): string => { - return html`${text}`; -}; - -export const CreateFileLink = (href: string, text: string): string => { - return html`${text}`; -}; - -export const ProgressBar = ( - total: number, - outdated: number, - missing: number, - { size = 20 }: { size?: number } = {} -): string => { - const outdatedSize = Math.round((outdated / total) * size); - const missingSize = Math.round((missing / total) * size); - const doneSize = size - outdatedSize - missingSize; - - const getBlocks = (size: number, type: 'missing' | 'outdated' | 'up-to-date') => { - const items = []; - for (let i = 0; i < size; i++) { - items.push(html`
`); - } - return items; - }; - - return html` - - `; -}; - -export const TitleParagraph = html` -

- If you're interested in helping us translate - docs.astro.build into one of the languages listed below, - you've come to the right place! This auto-updating page always lists all the content that could - use your help right now. -

-

- Before starting, please read our - i18n Guide - to learn about our translation process and how you can get involved. -

-`; - -/** - * Build an SVG file showing a summary of each language’s translation progress. - */ -export const SvgSummary = (config: LunariaConfig, status: LunariaStatus): string => { - const localeHeight = 56; // Each locale’s summary is 56px high. - const svgHeight = localeHeight * Math.ceil(config.locales.length / 2); - return html` - ${config.locales - .map((locale) => SvgLocaleSummary(status, locale)) - .sort((a, b) => b.progress - a.progress) - .map( - ({ svg }, index) => - html`${svg}` - )} - `; -}; - -function SvgLocaleSummary( - status: LunariaStatus, - { label, lang }: Locale -): { svg: string; progress: number } { - const missingFiles = status.filter( - (file) => - file.localizations.find((localization) => localization.lang === lang)?.status === 'missing' - ); - const outdatedFiles = status.filter((file) => { - const localization = file.localizations.find((localization) => localization.lang === lang); - if (!localization || localization.status === 'missing') { - return false; - } else if (file.type === 'dictionary') { - return 'missingKeys' in localization ? localization.missingKeys.length > 0 : false; - } else { - return ( - localization.status === 'outdated' || - ('missingKeys' in localization && localization.missingKeys.length > 0) - ); - } - }); - - const doneLength = status.length - outdatedFiles.length - missingFiles.length; - const barWidth = 184; - const doneFraction = doneLength / status.length; - const outdatedFraction = outdatedFiles.length / status.length; - const doneWidth = (doneFraction * barWidth).toFixed(2); - const outdatedWidth = ((outdatedFraction + doneFraction) * barWidth).toFixed(2); - - return { - progress: doneFraction, - svg: html`${label} (${lang}) - - ${missingFiles.length == 0 && outdatedFiles.length == 0 - ? '100% complete, amazing job! 🎉' - : html`${doneLength} done, ${outdatedFiles.length} outdated, ${missingFiles.length} - missing`} - - - - `, - }; -} diff --git a/scripts/lunaria/styles.css b/scripts/lunaria/styles.css new file mode 100644 index 0000000000000..2184a9cf9d50c --- /dev/null +++ b/scripts/lunaria/styles.css @@ -0,0 +1,42 @@ +/* Custom styles inlined into the Lunaria dashboard after its built-in styles. */ +:root { + --theme-navbar-height: 6rem; + --theme-mobile-toc-height: 4rem; + --theme-accent-secondary: hsla(22, 100%, 60%, 1); + --theme-text: hsla(250, 14%, 90%, 1); + --theme-bg: hsl(256, 27%, 19%); + --theme-bg-gradient-top: var(--theme-bg); + --theme-bg-gradient-bottom: hsl(251, 29%, 11%); + --theme-bg-gradient: linear-gradient( + 180deg, + var(--theme-bg-gradient-top), + var(--theme-bg-gradient-top) calc(var(--theme-navbar-height) + var(--theme-mobile-toc-height)), + var(--theme-bg-gradient-bottom) + ); + + --ln-color-link: #539bf5; + --ln-color-table-background: hsl(252, 34%, 25%); + --ln-color-table-border: hsl(252, 34%, 37%); + --ln-color-background: var(--theme-bg-gradient); + --ln-color-black: var(--theme-text); + --ln-color-missing: var(--ln-color-black); + --ln-color-outdated: #fb923c; + --ln-color-done: #c084fc; + + color-scheme: dark; +} + +p a { + color: var(--theme-accent-secondary); + text-decoration: underline; +} + +details summary:hover strong, +details summary:hover::marker { + color: var(--theme-accent-secondary); +} + +.create-button { + background-color: hsl(213deg 89% 64% / 20%); + border-radius: 0.5em; +} diff --git a/scripts/lunaria/styles.ts b/scripts/lunaria/styles.ts deleted file mode 100644 index 5ce1886aa51d1..0000000000000 --- a/scripts/lunaria/styles.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { html } from './components.ts'; - -export const BaseStyles = html` - -`; - -export const CustomStyles = html` - -`; diff --git a/scripts/lunaria/summary.ts b/scripts/lunaria/summary.ts new file mode 100644 index 0000000000000..c3af6f2a76cf1 --- /dev/null +++ b/scripts/lunaria/summary.ts @@ -0,0 +1,67 @@ +import type { Locale, LunariaConfig, LunariaStatus } from '@lunariajs/core'; +import { getDashboardStatus, getLocalization, html } from '@lunariajs/core/dashboard'; + +/** + * Build an SVG file showing a summary of each language’s translation progress. + * Embedded in the repository README and served at `https://i18n.docs.astro.build/summary.svg`. + */ +export const SvgSummary = (config: LunariaConfig, status: LunariaStatus): string => { + const localeHeight = 56; // Each locale’s summary is 56px high. + const svgHeight = localeHeight * Math.ceil(config.locales.length / 2); + return html` + ${config.locales + .map((locale) => SvgLocaleSummary(status, locale)) + .sort((a, b) => b.progress - a.progress) + .map(({ svg }, index) => { + const x = (index % 2) * 215; + const y = Math.floor(index / 2) * localeHeight; + return html`${svg}`; + })} + `; +}; + +function SvgLocaleSummary( + status: LunariaStatus, + { label, lang }: Locale +): { svg: string; progress: number } { + let missing = 0; + let outdated = 0; + for (const entry of status) { + const dashboardStatus = getDashboardStatus(getLocalization(entry, lang)); + if (dashboardStatus === 'missing') missing++; + else if (dashboardStatus === 'outdated') outdated++; + } + + const done = status.length - outdated - missing; + const barWidth = 184; + const doneFraction = done / status.length; + const outdatedFraction = outdated / status.length; + const doneWidth = (doneFraction * barWidth).toFixed(2); + const outdatedWidth = ((outdatedFraction + doneFraction) * barWidth).toFixed(2); + const summary = + missing === 0 && outdated === 0 + ? '100% complete, amazing job! 🎉' + : `${done} done, ${outdated} outdated, ${missing} missing`; + + return { + progress: doneFraction, + svg: html`${label} (${lang}) + ${summary} + + + `, + }; +} diff --git a/src/content/docs/de/recipes/tailwind-rendered-markdown.mdx b/src/content/docs/de/recipes/tailwind-rendered-markdown.mdx new file mode 100644 index 0000000000000..a301844b335af --- /dev/null +++ b/src/content/docs/de/recipes/tailwind-rendered-markdown.mdx @@ -0,0 +1,101 @@ +--- +title: Gestalte dein gerendertes Markdown mit dem Tailwind-CSS Typography-Plugin +description: Erfahre, wie du @tailwind/typography nutzt, um das Aussehen deines gerenderten Markdowns zu gestalten. +i18nReady: true +sidebar: + label: Nutzung des Tailwind Typography-Plugins +type: recipe +--- +import { Steps } from '@astrojs/starlight/components'; +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; + +Du kannst das Typography-Plugin von [Tailwind](https://tailwindcss.com) nutzen, um das Aussehen von gerenderten Markdown-Inhalten aus Quellen wie etwa [**Inhaltssammlungen**](/de/guides/content-collections/) von Astro zu gestalten. + +In diesem Rezept erfährst du, wie du eine wiederverwendbare Astro-Komponente erstellst, um das Aussehen deiner Markdown-Inhalte mithilfe der Utility-Klassen von Tailwind zu gestalten. + +## Voraussetzungen + +Ein Astro-Projekt, welches: + + - das [Vite-Plugin von Tailwind](/de/guides/styling/#tailwind) installiert hat. + - Astros [Sammlungen von Inhalten](/de/guides/content-collections/) verwendet. + +## Einrichtung des `@tailwindcss/typography`-Plugins + +Installiere zunächst das `@tailwindcss/typography`-Plugin unter Nutzung des von dir bevorzugten Paketmanagers. + + + + ```shell + npm install -D @tailwindcss/typography + ``` + + + ```shell + pnpm add -D @tailwindcss/typography + ``` + + + ```shell + yarn add --dev @tailwindcss/typography + ``` + + + +Füge nun das Paket als Plugin in deine Tailwind Konfigurationsdatei ein. + +```css title="src/styles/global.css" ins={2} +@import 'tailwindcss'; +@plugin '@tailwindcss/typography'; +``` + +## Anleitung + + +1. Erstelle eine ``-Komponente, um ein umschließendes `
`-Element mit einem `` für Ihren gerenderten Markdown-Text bereitzustellen. Fügen Sie die Stilklasse `prose` zusammen mit den gewünschten [Tailwind-Elementwandlern](https://tailwindcss.com/docs/typography-plugin#element-modifiers) im übergeordneten `
`-Element hinzu. + + ```astro title="src/components/Prose.astro" + --- + --- +
+ +
+ ``` + :::tip + Das `@tailwindcss/typography`-Plugin nutzt [**Elementwandler**](https://tailwindcss.com/docs/typography-plugin#element-modifiers) um das Aussehen von untergeordneten Elementen innerhalb eines mit der `prose`-Klasse ausgezeichneten Containers zu gestalten. + + Diese Elementwandler folgen der folgenden allgemeinen Syntax: + + ``` + prose-[Element]:anzuwendende-Klasse + ``` + + Beispielsweise weist `prose-h1:font-bold` allen `

`-Tags die Tailwind-Klasse `font-bold` zu. + ::: + +2. Rufen Sie Ihren Collection-Eintrag auf der Seite ab, auf der Sie Ihr Markdown rendern möchten. Übergeben Sie die Komponente `` aus `await render(entry)` als untergeordnete Komponente an ``, um Ihren Markdown-Inhalt mit Tailwind-Stilen zu umgeben. + + ```astro title="src/pages/index.astro" + --- + import Prose from '../components/Prose.astro'; + import Layout from '../layouts/Layout.astro'; + import { getEntry, render } from 'astro:content'; + + const entry = await getEntry('collection', 'entry'); + const { Content } = await render(entry); + --- + + + + + + ``` + + +## Ressourcen + +- [Dokumentation zum Tailwind Typography-Plugin (EN)](https://tailwindcss.com/docs/typography-plugin) diff --git a/src/content/docs/en/guides/authentication.mdx b/src/content/docs/en/guides/authentication.mdx index 25544d2ac4689..6c38149ed187a 100644 --- a/src/content/docs/en/guides/authentication.mdx +++ b/src/content/docs/en/guides/authentication.mdx @@ -4,7 +4,7 @@ description: An intro to authentication in Astro i18nReady: true --- -import { Steps } from '@astrojs/starlight/components' +import { Steps, Tabs, TabItem } from '@astrojs/starlight/components' import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro' import UIFrameworkTabs from '~/components/tabs/UIFrameworkTabs.astro' import ReadMore from '~/components/ReadMore.astro' @@ -66,12 +66,10 @@ Follow the [Better Auth Astro Guide](https://www.better-auth.com/docs/integratio ### Usage -Better Auth offers a `createAuthClient` helper for various frameworks, including Vanilla JS, React, Vue, Svelte, and Solid. +Better Auth offers a `createAuthClient()` helper for various frameworks, including Vanilla JS, React, Vue, Svelte, and Solid. For example, to create a client for React, import the helper from `'better-auth/react'`: - - ```ts title="src/lib/auth-client.ts" @@ -116,19 +114,26 @@ Once your client is set up, you can use it to authenticate users in your Astro c ```astro title="src/pages/index.astro" --- -import Layout from 'src/layouts/Base.astro'; +import Layout from "../layouts/Base.astro"; --- + ``` @@ -137,12 +142,12 @@ You can then use the `auth` object to get the user's session data in your server ```astro title="src/pages/index.astro" --- -import { auth } from "../../../lib/auth"; // import your Better Auth instance +import { auth } from "../lib/auth"; // import your Better Auth instance export const prerender = false; // Not needed in 'server' mode - + const session = await auth.api.getSession({ - headers: Astro.request.headers, + headers: Astro.request.headers, }); --- @@ -152,9 +157,9 @@ const session = await auth.api.getSession({ You can also use the `auth` object to protect your routes. The following example uses [Astro's advanced routing](/en/guides/routing/#advanced-routing) with [Hono](https://hono.dev/) to require an authenticated session for every route under `/dashboard`, redirecting to the home page otherwise: ```ts title="src/fetch.ts" -import { Hono } from "hono"; +import { Hono, type Context, type Next } from "hono"; import { astro } from "astro/hono"; -import { auth } from "../auth"; // import your Better Auth instance +import { auth } from "./lib/auth"; // import your Better Auth instance const app = new Hono(); @@ -167,12 +172,14 @@ app.use(astro()); export default app; -async function requireAuth(c, next) { - const session = await auth.api.getSession({ headers: c.req.raw.headers }); - if (!session) { - return c.redirect("/"); - } - return next(); +async function requireAuth(c: Context, next: Next) { + const session = await auth.api.getSession({ + headers: c.req.raw.headers, + }); + if (!session) { + return c.redirect("/"); + } + return next(); } ``` @@ -225,38 +232,67 @@ Clerk provides components that allow you to control the visibility of pages base ```astro title="src/pages/index.astro" --- -import Layout from 'src/layouts/Base.astro'; -import { SignedIn, SignedOut, UserButton, SignInButton } from '@clerk/astro/components'; +import Layout from "../layouts/Base.astro"; +import { Show, UserButton, SignInButton } from "@clerk/astro/components"; export const prerender = false; // Not needed in 'server' mode --- - - - - - - + + + + + + ``` -Clerk also allows you to protect routes on the server using middleware. Specify which routes are protected, and prompt unauthenticated users to sign in: +Clerk also allows you to protect routes on the server using middleware: -```ts title="src/middleware.ts" -import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'; + -const isProtectedRoute = createRouteMatcher([ - '/dashboard(.*)', - '/forum(.*)', -]); +1. Set `clerkMiddleware()` as the `onRequest` handler in your middleware: -export const onRequest = clerkMiddleware((auth, context) => { - if (!auth().userId && isProtectedRoute(context.request)) { - return auth().redirectToSignIn(); - } -}); -``` + ```ts title="src/middleware.ts" + import { clerkMiddleware } from "@clerk/astro/server"; + + export const onRequest = clerkMiddleware({ + /* options */ + }); + ``` + +2. Access the authentication state in your pages and API routes with `locals.auth()`. This allows you to check if a user is authenticated and take appropriate actions (e.g. redirecting to the sign-in page or returning a different response). + + + + ```astro title="src/pages/dashboard.astro" + --- + const { isAuthenticated, redirectToSignIn } = Astro.locals.auth(); + + if (!isAuthenticated) return redirectToSignIn(); + --- + +

Dashboard

+ ``` +
+ + ```ts title="src/pages/api/data.ts" + import type { APIRoute } from "astro"; + + export const GET: APIRoute = ({ locals }) => { + const { isAuthenticated, userId } = locals.auth(); + + if (!isAuthenticated) { + return new Response("Unauthorized", { status: 401 }); + } + + return Response.json({ userId }); + }; + ``` + +
+
### Next Steps @@ -286,7 +322,9 @@ export const onRequest = clerkMiddleware((auth, context) => { ## Scalekit -[Scalekit](https://scalekit.com/) is an authentication platform built for B2B and AI applications. It provides social login, enterprise SSO, magic links, and more — managing the full OAuth 2.0 / OIDC flow so you get back tokens and a user profile without building any login UI. A single Scalekit environment supports multiple applications, so users authenticate once and share the same session across all your properties (for example, `app.yourcompany.com` and `docs.yourcompany.com`). +[Scalekit](https://scalekit.com/) is an authentication platform for B2B and AI applications. It manages the full OAuth 2.0 and OIDC flow, supporting methods such as social login, enterprise SSO, and magic links. It then returns tokens and a user profile without requiring a custom login UI. + +A single Scalekit environment can support multiple applications. This allows you to authenticate once and share the same session across all your properties (e.g. `app.yourcompany.com` and `docs.yourcompany.com`). ### Guide diff --git a/src/content/docs/en/guides/backend/firebase.mdx b/src/content/docs/en/guides/backend/firebase.mdx index 9c0172a591926..0e36507d606aa 100644 --- a/src/content/docs/en/guides/backend/firebase.mdx +++ b/src/content/docs/en/guides/backend/firebase.mdx @@ -233,6 +233,7 @@ export const GET: APIRoute = async ({ request, cookies, redirect }) => { cookies.set("__session", sessionCookie, { path: "/", + maxAge: fiveDays / 1000, }); return redirect("/dashboard"); diff --git a/src/content/docs/en/guides/cms/apostrophecms.mdx b/src/content/docs/en/guides/cms/apostrophecms.mdx index 614d7ecc64a07..0543c7f873ed9 100644 --- a/src/content/docs/en/guides/cms/apostrophecms.mdx +++ b/src/content/docs/en/guides/cms/apostrophecms.mdx @@ -391,24 +391,24 @@ const { page, pieces } = Astro.props.aposData; To display individual blog posts, create a `BlogShow.astro` file in the Astro project `src/templates` folder with the following code: -This component uses the `` component to display any widgets added to the `content` area and the `authorName` and `publicationDate` content entered into the fields of the same names. +This component uses the `` component to display any widgets added to the `main` area and the `authorName` and `publicationDate` content entered into the fields of the same names. ```js title="src/templates/BlogShow.astro" --- -import AposArea from '@apostrophecms/apostrophe-astro/components/AposArea.astro'; -import dayjs from 'dayjs'; +import AposArea from "@apostrophecms/apostrophe-astro/components/AposArea.astro"; +import dayjs from "dayjs"; const { page, piece } = Astro.props.aposData; const { main } = piece; ---
-

{ piece.title }

-

Created by: { piece.authorName } +

{piece.title}

+

Created by: {piece.authorName}

- Released On { dayjs(piece.publicationDate).format('MMMM D, YYYY') } + Released On {dayjs(piece.publicationDate).format("MMMM D, YYYY")}

- +
``` diff --git a/src/content/docs/en/guides/cms/builderio.mdx b/src/content/docs/en/guides/cms/builderio.mdx index 24202be12d448..e00f9d7a2635e 100644 --- a/src/content/docs/en/guides/cms/builderio.mdx +++ b/src/content/docs/en/guides/cms/builderio.mdx @@ -199,9 +199,8 @@ For more ideas, read [Builder's troubleshooting guide](https://www.builder.io/c/ Add the following content to `src/pages/index.astro` in order to fetch and display a list of all post titles, each linking to its own page: -```astro title="src/pages/index.astro" {9} +```astro title="src/pages/index.astro" {8} --- - const builderAPIpublicKey = import.meta.env.BUILDER_API_PUBLIC_KEY; const builderModel = import.meta.env.BUILDER_BLOGPOST_MODEL; @@ -223,16 +222,15 @@ const { results: posts } = await fetch(
    { - posts.flatMap(({ data: { slug, title } }) => ( + posts.flatMap((post: any) => (
  • - {title} + {post.data.title}
  • )) }
- ``` Fetching via the content API returns an array of objects containing data for each post. The `fields` query parameter tells Builder which data is included (see highlighted code). `slug` and `title` should match the names of the custom data fields you've added to your Builder model. @@ -269,7 +267,7 @@ This file must contain: Each of these is highlighted in the following code snippet. -```astro title="src/pages/posts/[slug].astro" ins={2, 26, 33, 40, 51} +```astro title="src/pages/posts/[slug].astro" ins={2, 26, 33, 41, 52} --- export async function getStaticPaths() { const builderModel = import.meta.env.BUILDER_BLOGPOST_MODEL; @@ -280,17 +278,17 @@ export async function getStaticPaths() { apiKey: builderAPIpublicKey, fields: ["data.slug", "data.title"].join(","), cachebust: "true", - } + }, ).toString()}` ) .then((res) => res.json()) .catch // ...catch some errors...); (); - return posts.map(({ data: { slug, title } }) => ({ - params: { slug }, - props: { title }, - })) + return posts.map((post: any) => ({ + params: { slug: post.data.slug }, + props: { title: post.data.title }, + })); } const { slug } = Astro.params; const { title } = Astro.props; @@ -304,11 +302,12 @@ const { html: postHTML } = await fetch( url: encodedUrl, "query.data.slug": slug, cachebust: "true", - }).toString()}` + }).toString()}`, ) .then((res) => res.json()) .catch(); --- + {title} diff --git a/src/content/docs/en/guides/cms/buttercms.mdx b/src/content/docs/en/guides/cms/buttercms.mdx index e2e11d4d90ade..e3ac87ef4f066 100644 --- a/src/content/docs/en/guides/cms/buttercms.mdx +++ b/src/content/docs/en/guides/cms/buttercms.mdx @@ -81,18 +81,25 @@ import { butterClient } from "../lib/buttercms"; const response = await butterClient.content.retrieve(["shopitem"]); interface ShopItem { - name: string, - price: number, - description: string, + name: string; + price: number; + description: string; } -const items = response.data.data.shopitem as ShopItem[]; +const items = response?.data?.data.shopitem as ShopItem[]; --- + - {items.map(item =>
-

{item.name} - ${item.price}

-

-
)} + { + items.map((item) => ( +
+

+ {item.name} - ${item.price} +

+

+

+ )) + } ``` @@ -104,16 +111,17 @@ Similarly, you can [retrieve a page](https://buttercms.com/docs/api/#get-a-singl --- import { butterClient } from "../lib/buttercms"; const response = await butterClient.page.retrieve("*", "simple-page"); -const pageData = response.data.data; +const pageData = response?.data?.data; interface Fields { - seo_title: string, - headline: string, - hero_image: string, + seo_title: string; + headline: string; + hero_image: string; } -const fields = pageData.fields as Fields; +const fields = pageData?.fields as Fields; --- + {fields.seo_title} diff --git a/src/content/docs/en/guides/cms/cloudcannon.mdx b/src/content/docs/en/guides/cms/cloudcannon.mdx index 7b5d396af2b4d..35c78f523933c 100644 --- a/src/content/docs/en/guides/cms/cloudcannon.mdx +++ b/src/content/docs/en/guides/cms/cloudcannon.mdx @@ -165,13 +165,16 @@ const posts = await getCollection('blog'); ### Displaying a single entry -To display content from an individual post, you can import and use the `` component to [render your content to HTML](/en/guides/content-collections/#rendering-body-content): +To display content from an individual post, you can [`render()` your content to HTML](/en/guides/content-collections/#rendering-body-content) using the `` component: -```astro title="src/pages/blog/my-first-post.astro" {4-5} +```astro title="src/pages/blog/my-first-post.astro" {8,14} ", render" --- import { getEntry, render } from 'astro:content'; const entry = await getEntry('blog', 'my-first-post'); +if (!entry) { + throw new Error('Blog post not found'); +} const { Content } = await render(entry); --- diff --git a/src/content/docs/en/guides/cms/cosmic.mdx b/src/content/docs/en/guides/cms/cosmic.mdx index c9e099922cb1c..c8abdf25a6b72 100644 --- a/src/content/docs/en/guides/cms/cosmic.mdx +++ b/src/content/docs/en/guides/cms/cosmic.mdx @@ -102,7 +102,7 @@ PUBLIC_COSMIC_READ_KEY=YOUR_READ_KEY title={post.title} href={post.slug} body={post.metadata.excerpt} - tags={post.metadata.tags.map((tag) => tag)} + tags={post.metadata.tags.map((tag: any) => tag)} /> )) } @@ -151,7 +151,7 @@ const data = await getAllPosts() title={post.title} href={post.slug} body={post.metadata.excerpt} - tags={post.metadata.tags.map((tag) => tag)} + tags={post.metadata.tags.map((tag: any) => tag)} /> )) } @@ -206,7 +206,6 @@ const { post } = Astro.props format="webp" width={1200} height={675} - aspectRatio={16 / 9} quality={50} alt={`Cover image for the blog ${post.title}`} class={'my-12 rounded-md shadow-lg'} diff --git a/src/content/docs/en/guides/cms/drupal.mdx b/src/content/docs/en/guides/cms/drupal.mdx index 99b5851caa31c..98154d7e52142 100644 --- a/src/content/docs/en/guides/cms/drupal.mdx +++ b/src/content/docs/en/guides/cms/drupal.mdx @@ -383,8 +383,8 @@ With the setup above, you are now able to create a blog that uses Drupal as the import {DrupalJsonApiParams} from "drupal-jsonapi-params"; import type {TJsonApiBody} from "jsona/lib/JsonaTypes"; - import type { DrupalNode } from "../types"; - import {getArticles} from "../api/drupal"; + import type { DrupalNode } from "../../types"; + import { getArticles } from "../../api/drupal"; // Get all published articles. const articles = await getArticles(); diff --git a/src/content/docs/en/guides/cms/emdash.mdx b/src/content/docs/en/guides/cms/emdash.mdx index 142a226188080..602d0ddbb9b8a 100644 --- a/src/content/docs/en/guides/cms/emdash.mdx +++ b/src/content/docs/en/guides/cms/emdash.mdx @@ -4,13 +4,234 @@ description: Add content to your Astro project using EmDash as a CMS sidebar: label: EmDash type: cms -stub: true logo: emdash i18nReady: true --- +import { Steps } from '@astrojs/starlight/components'; +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; [EmDash](https://emdashcms.com/) is an open-source, full-stack CMS built specifically for Astro, adding database-backed content, an admin UI, a media library, menus, and taxonomies to your site. +:::tip +To start a **new Astro + EmDash project from scratch**, use the EmDash CLI to generate a pre-wired project: + + + + ```shell + npm create emdash@latest + ``` + + + ```shell + pnpm create emdash@latest + ``` + + + ```shell + yarn create emdash@latest + ``` + + +::: + +## Integrating with Astro + +EmDash runs within your Astro project and relies on a database. You can access the admin interface at `/_emdash/admin` to edit content. Your pages will display it by querying the database using [live content collections](/en/guides/content-collections/#live-content-collections). + +This guide uses the [Node.js adapter](/en/guides/integrations-guide/node/) and a local SQLite database. See the [EmDash docs for a list of supported databases](https://docs.emdashcms.com/deployment/database/). + +## Prerequisites + +- An existing Astro project (Astro 6 or later) configured [with an adapter](/en/guides/on-demand-rendering/) and [`output: "server"`](/en/reference/configuration-reference/#output). +- Node.js v22.16.0 or higher. + +## Installing dependencies + +React powers the EmDash admin interface and is a required dependency. If your project does not use React, install it using the `astro add` command for your package manager: + + + + ```shell + npx astro add react + ``` + + + ```shell + pnpm astro add react + ``` + + + ```shell + yarn astro add react + ``` + + + +You also need to install the EmDash package: + + + + ```shell + npm install emdash + ``` + + + ```shell + pnpm add emdash + ``` + + + ```shell + yarn add emdash + ``` + + + +## Adding the integration + +Add the `emdash()` integration to your Astro config file, and configure a database and a media storage backend: + +```js title="astro.config.mjs" ins={4-5, 12-18} +import { defineConfig } from "astro/config"; +import node from "@astrojs/node"; +import react from "@astrojs/react"; +import emdash, { local } from "emdash/astro"; +import { sqlite } from "emdash/db"; + +export default defineConfig({ + output: "server", + adapter: node({ mode: "standalone" }), + integrations: [ + react(), + emdash({ + database: sqlite({ url: "file:./data.db" }), + storage: local({ + directory: "./uploads", + baseUrl: "/_emdash/api/media/file", + }), + }), + ], +}); +``` + +## Adding the live collections loader + +Create a `src/live.config.ts` file so that Astro's content layer can resolve EmDash content: + +```ts title="src/live.config.ts" +import { defineLiveCollection } from "astro:content"; +import { emdashLoader } from "emdash/runtime"; + +export const collections = { + _emdash: defineLiveCollection({ loader: emdashLoader() }), +}; +``` + +The `_emdash` collection internally routes to your content types (e.g. posts and pages). Any existing file-based collections in `src/content.config.ts` keep working alongside it. + +## Running EmDash locally + +EmDash requires you to complete the setup wizard before testing your own pages. Until the setup is complete, there is no published content and queries return empty results. + + +1. Start Astro's dev server to initialize the database and launch the admin UI: + + + + ```shell + npm run dev + ``` + + + ```shell + pnpm run dev + ``` + + + ```shell + yarn run dev + ``` + + + + On first run, EmDash creates `data.db` with its schema and two default collections, `pages` and `posts`. + +2. Visit `http://localhost:4321/_emdash/admin` in the browser. EmDash redirects you to the setup wizard. + +3. In the **Site** step, enter a site title and an optional tagline. + +4. In the **Account** step, enter your email address and name. This creates the administrator account. + +5. In the **Sign In** step, secure your account. Choose **Create Passkey** to register a passkey with your device's biometric authentication, security key, or PIN. + +6. Sign in with your new passkey to reach the dashboard. + + +## Creating your first post + + +1. In the dashboard, click the **+ Post** button. + +2. Add a title and some content. EmDash stores rich text as [Portable Text](https://github.com/portabletext/portabletext), edited in a block editor. A URL slug is generated from the title and can be edited in the sidebar. + +3. Click **Save**, then **Publish**. Only published posts are visible to site visitors. + + +## Rendering EmDash content + +Query your content with `getEmDashCollection()` and `getEmDashEntry()`. Both follow the live collections pattern and return results at request time, so published changes appear without a rebuild. + +### Displaying a list of posts + +The following example displays a list of all published post titles, each linking to an individual post page: + +```astro title="src/pages/blog.astro" +--- +import { getEmDashCollection } from "emdash"; + +const { entries: posts } = await getEmDashCollection("posts", { + status: "published", +}); +--- + +``` + +### Displaying a single post + +To display content from an individual post, fetch it by its slug and render the Portable Text content with the `` component: + +```astro title="src/pages/posts/[...slug].astro" +--- +import { getEmDashEntry } from "emdash"; +import { PortableText } from "emdash/ui"; + +const { slug } = Astro.params; +const { entry: post } = await getEmDashEntry("posts", slug); + +if (!post) { + return Astro.redirect("/404"); +} +--- +
+

{post.data.title}

+ +
+``` + +See the [EmDash querying guide](https://docs.emdashcms.com/guides/querying-content/) for more information on filtering, pagination, previewing drafts, and visual editing. + +## Deploying EmDash + Astro + +EmDash deploys together with your site as a single Astro project. Choose a host that supports your adapter, and provision a production database and media storage. + +See the [EmDash deployment guide for Node.js](https://docs.emdashcms.com/deployment/nodejs/) and the [EmDash deployment guide for Cloudflare](https://docs.emdashcms.com/deployment/cloudflare/) for specific instructions. You can also visit Astro's [deployment guides](/en/guides/deploy/) and follow the instructions to deploy with your preferred hosting provider. + ## Official Resources - [EmDash Documentation for Astro Developers](https://docs.emdashcms.com/coming-from/astro/) diff --git a/src/content/docs/en/guides/cms/ghost.mdx b/src/content/docs/en/guides/cms/ghost.mdx index ef3f2371ded9c..f9123bb733111 100644 --- a/src/content/docs/en/guides/cms/ghost.mdx +++ b/src/content/docs/en/guides/cms/ghost.mdx @@ -182,7 +182,7 @@ const posts = await ghostClient.posts { - posts.map((post) => ( + posts?.map((post) => (

{post.title}

@@ -223,7 +223,7 @@ export async function getStaticPaths() { console.error(err); }); - return posts.map((post) => { + return posts?.map((post) => { return { params: { slug: post.slug, @@ -252,7 +252,7 @@ export async function getStaticPaths() { .catch((err) => { console.error(err); }); - return posts.map((post) => { + return posts?.map((post) => { return { params: { slug: post.slug, @@ -294,7 +294,7 @@ To deploy your site visit our [deployment guide](/en/guides/deploy/) and follow - + diff --git a/src/content/docs/en/guides/cms/keystatic.mdx b/src/content/docs/en/guides/cms/keystatic.mdx index c8fe130303978..7eb653de0397e 100644 --- a/src/content/docs/en/guides/cms/keystatic.mdx +++ b/src/content/docs/en/guides/cms/keystatic.mdx @@ -172,7 +172,7 @@ Visit `http://127.0.0.1:4321/keystatic` in the browser to see the Keystatic Admi 5. Navigate to that file in your code editor and verify that you can see the Markdown content you entered. For example: - ```markdown + ```markdown title="src/content/posts/my-first-post.mdoc" --- title: My First Post --- @@ -189,7 +189,7 @@ Visit `http://127.0.0.1:4321/keystatic` in the browser to see the Keystatic Admi The following example displays a list of each post title, with a link to an individual post page. -```tsx {4} +```astro title="src/pages/posts/index.astro" {4} --- import { getCollection } from 'astro:content' @@ -206,21 +206,23 @@ const posts = await getCollection('posts') ### Displaying a single entry -To display content from an individual post, you can import and use the `` component to [render your content to HTML](/en/guides/content-collections/#rendering-body-content): +To display content from an individual post, you can [`render()` your content to HTML](/en/guides/content-collections/#rendering-body-content) using the `` component: -```tsx {4-5} +```astro title="src/pages/posts/my-first-post.astro" {8,13} ", render" --- -import { getEntry } from 'astro:content' +import { getEntry, render } from "astro:content"; -const post = await getEntry('posts', 'my-first-post') -const { Content } = await post.render() +const post = await getEntry("posts", "my-first-post"); +if (!post) { + throw new Error("Post not found"); +} +const { Content } = await render(post); ---

{post.data.title}

- ``` For more information on querying, filtering, displaying your collections content and more, see the full content [collections documentation](/en/guides/content-collections/). diff --git a/src/content/docs/en/guides/cms/kontent-ai.mdx b/src/content/docs/en/guides/cms/kontent-ai.mdx index c49b2145cc3fd..d375c3b3e2195 100644 --- a/src/content/docs/en/guides/cms/kontent-ai.mdx +++ b/src/content/docs/en/guides/cms/kontent-ai.mdx @@ -376,6 +376,7 @@ export async function getStaticPaths() { .items() .type(contentTypes.blog_post.codename) .toPromise() +} --- ``` @@ -437,6 +438,7 @@ const blogPost: BlogPost = Astro.props.blogPost + ``` @@ -516,6 +518,7 @@ try { + ``` diff --git a/src/content/docs/en/guides/cms/preprcms.mdx b/src/content/docs/en/guides/cms/preprcms.mdx index 5b1a073509a2f..df29613e5f5fd 100644 --- a/src/content/docs/en/guides/cms/preprcms.mdx +++ b/src/content/docs/en/guides/cms/preprcms.mdx @@ -99,24 +99,22 @@ You will fetch your data from Prepr by writing queries to interact with its Grap ``` 3. To display a linked list of your blog posts on a page, import and execute your query, including the necessary Prepr endpoint. You will then have access to all your posts titles and their slugs to render to the page. (In the next step, you will [create individual pages for your blog posts](#creating-individual-blog-post-pages).) - ```astro title="src/pages/index.astro" ins={3-4, 6-8, 15-23} + ```astro title="src/pages/index.astro" ins={3-4, 6-8, 13-21} --- - import Layout from '../layouts/Layout.astro'; - import { Prepr } from '../lib/prepr.js'; - import GetArticles from '../queries/get-articles.js'; + import Layout from "../layouts/Layout.astro"; + import { Prepr } from "../lib/prepr.js"; + import GetArticles from "../queries/get-articles.js"; - const response = await Prepr(GetArticles) - const { data } = await response.json() - const articles = data.Articles + const response = await Prepr(GetArticles); + const { data } = await response.json(); + const articles = data.Articles; --- -

- My blog site -   

-   
    +

    My blog site

    +
      { - articles.items.map((post) => ( + articles.items.map((post: any) => (
    • {post.title}
    • @@ -148,7 +146,7 @@ To create a page for each blog post, you will execute a new GraphQL query on a [ 1. Create a file called `get-article-by-slug.js` in the `queries` folder and add the following to query a specific article by its slug and return data such as the article `title` and `content`: - ```js title="src/lib/queries/get-article-by-slug.js" + ```js title="src/queries/get-article-by-slug.js" const GetArticleBySlug = ` query ($slug: String) {    Article (slug: $slug) { @@ -180,30 +178,26 @@ To create a page for each blog post, you will execute a new GraphQL query on a [ 2. Inside the `src/pages` folder, create a file called `[…slug].astro`. Add the following code to import and execute the query from the previous step and display the retrieved article: ```astro title="src/pages/[...slug].astro" --- - import Layout from '../layouts/Layout.astro'; - import {Prepr} from '../lib/prepr.js'; - import GetArticleBySlug from '../queries/get-article-by-slug.js'; + import Layout from "../layouts/Layout.astro"; + import { Prepr } from "../lib/prepr.js"; + import GetArticleBySlug from "../queries/get-article-by-slug.js"; const { slug } = Astro.params; - const response = await Prepr(GetArticleBySlug, {slug}) - const { data } = await response.json() - const article = data.Article + const response = await Prepr(GetArticleBySlug, { slug }); + const { data } = await response.json(); + const article = data.Article; ---

      {article.title}

      { - article.content.map((content) => ( + article.content.map((content: any) => (
      - { - content.__typename === "Assets" && - - } - { - content.__typename === 'Text' && -
      - } + {content.__typename === "Assets" && ( + + )} + {content.__typename === "Text" &&
      }
      )) } diff --git a/src/content/docs/en/guides/cms/statamic.mdx b/src/content/docs/en/guides/cms/statamic.mdx index 6dd2a97041605..cec8d0883cc54 100644 --- a/src/content/docs/en/guides/cms/statamic.mdx +++ b/src/content/docs/en/guides/cms/statamic.mdx @@ -48,7 +48,7 @@ const posts = await res.json() ---

      Astro + Statamic 🚀

      { - posts.map((post) => ( + posts.map((post: any) => (

      )) @@ -85,8 +85,8 @@ const graphqlQuery = { } `, variables: { - page: page, - locale: locale, + page: "my-current-page", + locale: "my-locale", }, }; @@ -101,7 +101,7 @@ const entries = data?.entries; ---

      Astro + Statamic 🚀

      { - entries.data.map((post) => ( + entries.data.map((post: any) => (

      )) diff --git a/src/content/docs/en/guides/cms/storyblok.mdx b/src/content/docs/en/guides/cms/storyblok.mdx index 6d678a7b4c894..8632e80366010 100644 --- a/src/content/docs/en/guides/cms/storyblok.mdx +++ b/src/content/docs/en/guides/cms/storyblok.mdx @@ -277,7 +277,7 @@ const { blok } = Astro.props

      { - blok.body?.map((blok) => { + blok.body?.map((blok: any) => { return }) } @@ -306,38 +306,43 @@ const content = renderRichText(blok.content) It uses the `useStoryblokApi` hook to fetch all the stories with the content type of `blogPost`. It uses the `version` query parameter to fetch the draft versions of the stories when in development mode and the published versions when building for production. `Astro.props` is used to set up the editor in Storyblok. Additional props can also be passed to your component here, if needed. + ```astro title="src/storyblok/BlogPostList.astro" --- -import { storyblokEditable } from '@storyblok/astro' -import { useStoryblokApi } from '@storyblok/astro' +import { storyblokEditable } from "@storyblok/astro"; +import { useStoryblokApi } from "@storyblok/astro"; const storyblokApi = useStoryblokApi(); -const { data } = await storyblokApi.get('cdn/stories', { +const { data } = await storyblokApi.get("cdn/stories", { version: import.meta.env.DEV ? "draft" : "published", - content_type: 'blogPost', -}) + content_type: "blogPost", +}); -const posts = data.stories.map(story => { +const posts = data.stories.map((story: any) => { return { title: story.content.title, - date: new Date(story.published_at).toLocaleDateString("en-US", {dateStyle: "full"}), + date: new Date(story.published_at).toLocaleDateString("en-US", { + dateStyle: "full", + }), description: story.content.description, slug: story.full_slug, - } -}) + }; +}); -const { blok } = Astro.props +const { blok } = Astro.props; ---
        - {posts.map(post => ( -
      • - - {post.title} -

        {post.description}

        -
      • - ))} + { + posts.map((post: any) => ( +
      • + + {post.title} +

        {post.description}

        +
      • + )) + }
      ``` @@ -405,8 +410,8 @@ Create a new directory `src/pages/blog/` and add a new file called `[...slug].as ```astro title="src/pages/blog/[...slug].astro" --- -import { useStoryblokApi } from '@storyblok/astro' -import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro' +import { useStoryblokApi } from "@storyblok/astro"; +import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro"; export async function getStaticPaths() { const sbApi = useStoryblokApi(); @@ -418,7 +423,7 @@ export async function getStaticPaths() { const stories = Object.values(data.stories); - return stories.map((story) => { + return stories.map((story: any) => { return { params: { slug: story.slug }, }; diff --git a/src/content/docs/en/guides/cms/strapi.mdx b/src/content/docs/en/guides/cms/strapi.mdx index 5a83bc07714f9..bce096853f67d 100644 --- a/src/content/docs/en/guides/cms/strapi.mdx +++ b/src/content/docs/en/guides/cms/strapi.mdx @@ -128,6 +128,11 @@ export default interface Article { createdAt: string; updatedAt: string; publishedAt: string; + image: { + data: { + url: string; + }; + }; } ``` @@ -268,8 +273,9 @@ const article = Astro.props; Create the template for each page using the properties of each post object. -```astro title="src/pages/blog/[slug].astro" ins={21-43} +```astro title="src/pages/blog/[slug].astro" ins={22-44} --- +import MyMarkdownComponent from '../../components/MyMarkdownComponent.astro'; import fetchApi from '../../lib/strapi'; import type Article from '../../interfaces/article'; @@ -325,8 +331,9 @@ Create the `src/pages/blog/[slug].astro` file: ```astro title="src/pages/blog/[slug].astro" --- -import fetchApi from '../../../lib/strapi'; -import type Article from '../../../interfaces/article'; +import MyMarkdownComponent from '../../components/MyMarkdownComponent.astro'; +import fetchApi from '../../lib/strapi'; +import type Article from '../../interfaces/article'; const { slug } = Astro.params; diff --git a/src/content/docs/en/guides/cms/tina-cms.mdx b/src/content/docs/en/guides/cms/tina-cms.mdx index 06a70801dbaad..a2aa7acb07b2a 100644 --- a/src/content/docs/en/guides/cms/tina-cms.mdx +++ b/src/content/docs/en/guides/cms/tina-cms.mdx @@ -86,11 +86,11 @@ To get started, you'll need an existing Astro project. Editing the “Hello, World!” post will update the `content/posts/hello-world.md` file in your project directory. -4. Set up your Tina collections by editing the `schema.collections` property in `.tina/config.ts`. +4. Set up your Tina collections by editing the `schema.collections` property in `tina/config.ts`. For example, you can add a required "date posted" frontmatter property to our posts: - ```js title=".tina/config.ts" ins={35-40} + ```js title="tina/config.ts" ins={35-40} import { defineConfig } from "tinacms"; // Your hosting provider likely exposes this as an environment variable diff --git a/src/content/docs/en/guides/cms/umbraco.mdx b/src/content/docs/en/guides/cms/umbraco.mdx index 91f7910b4bfdb..f54a30d9ede6c 100644 --- a/src/content/docs/en/guides/cms/umbraco.mdx +++ b/src/content/docs/en/guides/cms/umbraco.mdx @@ -57,7 +57,7 @@ const articles = await res.json(); ---

      Astro + Umbraco 🚀

      { - articles.items.map((article) => ( + articles.items.map((article: any) => (

      {article.name}

      {article.properties.articleDate}

      @@ -158,7 +158,7 @@ Note that the `params` property, which generates the URL path of the page, conta Add the following code to `[...slug].astro` which will create your individual blog post pages: -```astro title="[...slug].astro" +```astro title="src/pages/[...slug].astro" --- import Layout from '../layouts/Layout.astro'; diff --git a/src/content/docs/en/guides/cms/wordpress.mdx b/src/content/docs/en/guides/cms/wordpress.mdx index b56aac9bf72ca..8bc76efceb249 100644 --- a/src/content/docs/en/guides/cms/wordpress.mdx +++ b/src/content/docs/en/guides/cms/wordpress.mdx @@ -43,7 +43,7 @@ const posts = await res.json(); ---

      Astro + WordPress 🚀

      { - posts.map((post) => ( + posts.map((post: any) => (

      )) @@ -95,11 +95,12 @@ import Layout from "../layouts/Layout.astro"; let res = await fetch("https://norian.studio/wp-json/wp/v2/dinos"); let posts = await res.json(); --- +

      List of Dinosaurs

      { - posts.map((post) => ( + posts.map((post: any) => (

      @@ -118,7 +119,7 @@ The page `src/pages/dinos/[slug].astro` [dynamically generates a page](/en/guide ```astro title="/src/pages/dinos/[slug].astro" --- -import Layout from '../../layouts/Layout.astro'; +import Layout from "../../layouts/Layout.astro"; const { slug } = Astro.params; @@ -131,12 +132,13 @@ export async function getStaticPaths() { let data = await fetch("https://norian.studio/wp-json/wp/v2/dinos"); let posts = await data.json(); - return posts.map((post) => ({ + return posts.map((post: any) => ({ params: { slug: post.slug }, props: { post: post }, })); } --- +

      diff --git a/src/content/docs/en/guides/deploy/ishosting.mdx b/src/content/docs/en/guides/deploy/ishosting.mdx new file mode 100644 index 0000000000000..212033b568e98 --- /dev/null +++ b/src/content/docs/en/guides/deploy/ishosting.mdx @@ -0,0 +1,16 @@ +--- +title: Deploy your Astro Site to is*hosting +description: How to deploy your Astro site to the web using is*hosting +sidebar: + label: is*hosting +type: deploy +logo: ishosting +supports: ['ssr', 'static'] +i18nReady: true +--- + +[is\*hosting](https://ishosting.com/) is a hosting provider offering VPS and dedicated servers in 40+ locations that you can use to self-host a static or server-rendered (SSR) Astro site. + +## Official Resources + +- [is\*hosting guide: deploy Astro on a VPS (static and SSR)](https://blog.ishosting.com/en/astro-on-vps) diff --git a/src/content/docs/en/guides/integrations-guide/cloudflare.mdx b/src/content/docs/en/guides/integrations-guide/cloudflare.mdx index b938087f110b1..2ba16e12db204 100644 --- a/src/content/docs/en/guides/integrations-guide/cloudflare.mdx +++ b/src/content/docs/en/guides/integrations-guide/cloudflare.mdx @@ -465,16 +465,20 @@ When using these handlers in your worker entrypoint, they replace the functional For use with [`astro/fetch`](/en/reference/modules/astro-fetch/). The `cf()` function imported from `@astrojs/cloudflare/fetch` receives a [`FetchState`](/en/reference/modules/astro-fetch/#fetchstate), the Cloudflare `env`, and the `ExecutionContext`. It returns a `Response` for static asset hits, or `undefined` when the request should continue to Astro rendering: +

      + +Pass the same `FetchState` and the response from your Astro pipeline to `finalize()` before returning it. This applies cookies produced during rendering and the adapter's default Cloudflare CDN cache headers to the response. + ```ts title="src/worker.ts" import { astro, FetchState } from 'astro/fetch'; -import { cf } from '@astrojs/cloudflare/fetch'; +import { cf, finalize } from '@astrojs/cloudflare/fetch'; export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { const state = new FetchState(request); const asset = await cf(state, env, ctx); if (asset) return asset; - return astro(state); + return finalize(state, await astro(state)); }, }; ``` @@ -485,6 +489,8 @@ export default { For use with [`astro/hono`](/en/reference/modules/astro-hono/). The `cf()` function imported from `@astrojs/cloudflare/hono` returns a Hono middleware that reads `env` and `executionCtx` from the Hono context automatically: +In `@astrojs/cloudflare` v14.3.0 and later, this middleware also finalizes the response after downstream Hono handlers run. Cookies produced during rendering and the adapter's default Cloudflare CDN cache headers are applied automatically. + ```ts title="src/worker.ts" import { Hono } from 'hono'; import { actions, middleware, pages, i18n } from 'astro/hono'; diff --git a/src/content/docs/en/guides/media/cloudinary.mdx b/src/content/docs/en/guides/media/cloudinary.mdx index cf6dfd189f74f..e35e9999d9135 100644 --- a/src/content/docs/en/guides/media/cloudinary.mdx +++ b/src/content/docs/en/guides/media/cloudinary.mdx @@ -121,7 +121,7 @@ The Cloudinary Astro SDK provides the `cldAssetsLoader` content loader to load C To load a collection of images or videos, set `loader: cldAssetsLoader ({})` with a `folder`, if required: -```jsx title="config.ts" +```jsx title="content.config.ts" import { defineCollection } from 'astro:content'; import { cldAssetsLoader } from 'astro-cloudinary/loaders'; diff --git a/src/content/docs/en/guides/media/imagekit.mdx b/src/content/docs/en/guides/media/imagekit.mdx index 9c90d0feed52d..1ae6153ec573c 100644 --- a/src/content/docs/en/guides/media/imagekit.mdx +++ b/src/content/docs/en/guides/media/imagekit.mdx @@ -428,7 +428,7 @@ const gallery = defineCollection({ })); }, schema: z.object({ - url: z.string().url(), + url: z.url(), width: z.number(), height: z.number(), name: z.string(), diff --git a/src/content/docs/en/guides/media/mux.mdx b/src/content/docs/en/guides/media/mux.mdx index 1600f85a5be7e..522499abf2017 100644 --- a/src/content/docs/en/guides/media/mux.mdx +++ b/src/content/docs/en/guides/media/mux.mdx @@ -86,6 +86,10 @@ You will need the `playbackId` for your asset, which can be found in your Mux da All other [options to control the Mux web player](https://www.mux.com/docs/guides/player-api-reference/?utm_campaign=21819274-Astro&utm_source=astro-docs) (e.g. hide or display controls, style elements, disable cookies) are optional: ```astro title="src/components/StarlightVideo.astro" +--- +import { MuxPlayer } from "@mux/mux-player-astro"; +--- + - ``` Every live stream is recorded and saved on Mux as a video asset for future on-demand playback. @@ -211,7 +218,7 @@ const mux = new Mux ({ To fetch information about your video to use in your Astro project, provide the video's `ASSET_ID` (available in the Mux dashboard) to the `retrieve()` helper function. This will allow you to pass values to both your Mux components and your HTML template, such as the video's title or duration: -```astro +```astro title="src/components/StarlightVideo.astro" --- import Mux from "@mux/mux-node"; import { MuxPlayer } from "@mux/mux-player-astro"; @@ -278,9 +285,9 @@ Install the Astro version of Mux Uploader using your preferred package manager: Before uploading a video, make sure you have your [Mux API access tokens](#mux-environment-api-access) configured. With those in place, you can use the `create()` function from the Mux Node SDK to start a new video upload: -```astro +```astro title="src/components/VideoUploader.astro" --- -import Layout from '../../layouts/Layout.astro'; +import Layout from '../layouts/Layout.astro'; import Mux from "@mux/mux-node"; import { MuxUploader } from "@mux/mux-uploader-astro"; @@ -306,7 +313,7 @@ const upload = await mux.video.uploads.create({ You can customize the functionality and appearance of the `` with additional component attributes. In addition to styling your element, this allows you to control options such as the ability to pause a download or set a maximum file size. -```astro +```astro title="src/components/VideoUploader.astro" --- import { MuxUploader } from '@mux/mux-uploader-astro'; --- @@ -331,7 +338,7 @@ Mux Uploader provides a feature-rich, dynamic UI that changes based on the curre You can listen for these events and handle them in your Astro component with [client-side scripts](/en/guides/client-side-scripts/). A `MuxUploaderElement` type is also available. -```astro +```astro title="src/components/VideoUploader.astro" --- import { MuxUploader } from '@mux/mux-uploader-astro'; --- @@ -373,4 +380,4 @@ For the full API and webhook reference, usage guides, and information about addi - [`@mux/mux-player-astro` API reference](https://github.com/muxinc/elements/blob/main/packages/mux-player-astro/README.md) - [`@mux/mux-uploader-astro` API reference](https://github.com/muxinc/elements/blob/main/packages/mux-uploader-astro/REFERENCE.md) - [Building a video uploader with Mux and Astro (YouTube)](https://www.youtube.com/watch?v=aaL1k5FsWfE) -- [Astro uploader and player code example (GitHub)](https://github.com/muxinc/examples/tree/main/astro-uploader-and-player) \ No newline at end of file +- [Astro uploader and player code example (GitHub)](https://github.com/muxinc/examples/tree/main/astro-uploader-and-player) diff --git a/src/content/docs/en/reference/cache-provider-reference.mdx b/src/content/docs/en/reference/cache-provider-reference.mdx index fa6375fdd7391..b46bd571470e0 100644 --- a/src/content/docs/en/reference/cache-provider-reference.mdx +++ b/src/content/docs/en/reference/cache-provider-reference.mdx @@ -371,10 +371,45 @@ The incoming `request` is passed as a second argument so a provider can read the

      -**Type:** (context: \{ request: Request; url: URL; waitUntil?: (promise: Promise\) => void \}, next: MiddlewareNext) => Promise\ +**Type:** (context: \{ request: Request; url: URL; waitUntil?: (promise: Promise\) => void; logger: AstroRuntimeLogger \}, next: MiddlewareNext) => Promise\

      -Intercepts requests to implement runtime caching. The `context` includes a `waitUntil()` function (when available in the runtime) for background work such as stale-while-revalidate. +An optional hook that intercepts a request before Astro generates the matching route. It receives a `context` object as its first argument and a callback to call the `next()` middleware in the chain. + +The `context` contains the following properties: +- `request`: the incoming [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. +- `url`: a normalized [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) derived from the request. +- `waitUntil()`: when available in the runtime, a function to define background work, such as revalidating a stale cache entry. +- `logger`: since Astro v7.3.0, a [`logger`](/en/reference/api-reference/#logger) instance that respects the [configured logging destination](/en/reference/configuration-reference/#logger-options) + +The following example implements a minimal `onRequest()` hook that logs each URL added to the cache: + +```ts title="my-provider/runtime.ts" ins={7-17} +import type { CacheProviderFactory } from 'astro'; + +const factory: CacheProviderFactory = (config) => { + const cache = new Map(); + return { + name: 'my-cache-provider', + async onRequest({ request, url, waitUntil, logger }, next) { + if (request.method !== 'GET') return next(); + + const cached = cache.get(url); + if (cached) return cached; + + const response = await next(); + cache.set(url, response.clone()); + logger.info(`Cached response for ${url}.`); + return response; + }, + async invalidate() { + // ... + }, + }; +}; + +export default factory; +``` #### `CacheProvider.invalidate()` diff --git a/src/content/docs/en/reference/cli-reference.mdx b/src/content/docs/en/reference/cli-reference.mdx index 98442f1ecac00..eb21ab4049c38 100644 --- a/src/content/docs/en/reference/cli-reference.mdx +++ b/src/content/docs/en/reference/cli-reference.mdx @@ -196,25 +196,7 @@ The following hotkeys can be used in the terminal where the Astro development se - `o + enter` to open your Astro site in the browser. - `q + enter` to quit the development server. -

      Flags

      - -

      - -The command accepts [common flags](#common-flags) and the following additional flags. - -#### `--ignore-lock` - -

      - -Starts the dev server without checking or writing the lock file used to detect other running dev servers. This allows a new dev server to start alongside one that's already running for the same project, instead of erroring. - -```shell -astro dev --ignore-lock --port 4322 -``` - -The new server is not tracked by the [`stop`, `status`, or `logs` subcommands](#common-subcommands). - -When combined with `--background` (including when triggered by an AI coding agent) or `--force`, an error is thrown, as both rely on the lock file. +The command can be combined with the [common flags](#common-flags) and [common subcommands](#common-subcommands) to further control the dev experience. ## `astro build` @@ -240,7 +222,7 @@ The following hotkeys can be used in the terminal where the Astro preview server - `o` + `enter` to open your Astro site in the browser. - `q` + `enter` to quit the preview server. -The `astro preview` command can be combined with the [common flags](#common-flags) documented below to further control the preview experience. Since v7.2.0, it also accepts the [`--background` flag](#--background) and the [`stop`, `status`, and `logs` subcommands](#common-subcommands) to manage a background preview server. +The command can be combined with the [common flags](#common-flags) and [common subcommands](#common-subcommands) to further control the preview experience. ## `astro check` @@ -560,6 +542,20 @@ astro dev --background --force Enables [JSON logging](/en/reference/logger-reference/#loghandlersjson), which is useful for machine-readable output. +### `--ignore-lock` + +

      + +Prevents checking for the existence of a lock file and the need to write one. This allows a new dev server or, since v7.3.0, a preview server to start alongside an already running server, instead of erroring. + +```shell +astro dev --ignore-lock --port 4322 +``` + +The new server is not tracked by the [common subcommands](#common-subcommands). + +When combined with [`--background`](#--background) or [`--force`](#--force-string), an error is thrown, as both rely on the lock file. + ## Global flags Use these flags to get information about the `astro` CLI. diff --git a/src/content/docs/en/reference/errors/redirect-with-no-location.mdx b/src/content/docs/en/reference/errors/redirect-with-no-location.mdx index a71b66e5bbc61..41157653f393d 100644 --- a/src/content/docs/en/reference/errors/redirect-with-no-location.mdx +++ b/src/content/docs/en/reference/errors/redirect-with-no-location.mdx @@ -13,6 +13,8 @@ import DontEditWarning from '~/components/DontEditWarning.astro' +> **RedirectWithNoLocation**: The redirect `Response` has no `Location` header. Use `Astro.redirect()` to create redirects, or add a `Location` header to the `Response`. + ## What went wrong? A redirect must be given a location with the `Location` header. diff --git a/src/content/docs/en/reference/image-service-reference.mdx b/src/content/docs/en/reference/image-service-reference.mdx index 344e665ad3bdd..1998346d0b77e 100644 --- a/src/content/docs/en/reference/image-service-reference.mdx +++ b/src/content/docs/en/reference/image-service-reference.mdx @@ -31,21 +31,21 @@ An external service points to a remote URL to be used as the `src` attribute of import type { ExternalImageService, ImageTransform, AstroConfig } from "astro"; const service: ExternalImageService = { - validateOptions(options: ImageTransform, imageConfig: AstroConfig['image']) { + validateOptions(options: ImageTransform, imageConfig: AstroConfig['image'], logger) { const serviceConfig = imageConfig.service.config; // Enforce the user set max width. if (options.width && options.width > serviceConfig.maxWidth) { - console.warn(`Image width ${options.width} exceeds max width ${serviceConfig.maxWidth}. Falling back to max width.`); + logger.warn(`Image width ${options.width} exceeds max width ${serviceConfig.maxWidth}. Falling back to max width.`); options.width = serviceConfig.maxWidth; } return options; }, - getURL(options, imageConfig) { + getURL(options, imageConfig, logger) { return `https://mysupercdn.com/${options.src}?q=${options.quality}&w=${options.width}&h=${options.height}`; }, - getHTMLAttributes(options, imageConfig) { + getHTMLAttributes(options, imageConfig, logger) { const { src, format, quality, ...attributes } = options; return { ...attributes, @@ -68,7 +68,7 @@ import type { ImageTransform, LocalImageService, AstroConfig } from "astro"; import { mySuperLibraryThatEncodesImages } from "@example/my-super-library"; const service: LocalImageService = { - getURL(options: ImageTransform, imageConfig) { + getURL(options: ImageTransform, imageConfig, logger) { const searchParams = new URLSearchParams(); searchParams.append('href', typeof options.src === "string" ? options.src : options.src.src); options.width && searchParams.append('w', options.width.toString()); @@ -79,7 +79,7 @@ const service: LocalImageService = { // Or use the built-in endpoint, which will call your parseURL and transform functions: // return `/_image?${searchParams}`; }, - parseURL(url: URL, imageConfig) { + parseURL(url: URL, imageConfig, logger) { const params = url.searchParams; return { src: params.get('href')!, @@ -89,14 +89,14 @@ const service: LocalImageService = { quality: params.get('q'), }; }, - async transform(inputBuffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig) { + async transform(inputBuffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig, logger) { const { buffer } = await mySuperLibraryThatEncodesImages(options); return { data: buffer, format: options.format, }; }, - getHTMLAttributes(options, imageConfig) { + getHTMLAttributes(options, imageConfig, logger) { let targetWidth = options.width; let targetHeight = options.height; if (typeof options.src === "object") { @@ -141,7 +141,7 @@ import { getConfiguredImageService, imageConfig } from "astro:assets"; import * as mime from "mrmime"; import { getImageBuffer } from "./my-custom-image-fetcher.js"; -export const GET: APIRoute = async ({ request }) => { +export const GET: APIRoute = async ({ request, logger }) => { const imageService = await getConfiguredImageService(); if (!isLocalService(imageService)) { @@ -154,6 +154,7 @@ export const GET: APIRoute = async ({ request }) => { const imageTransform = await imageService.parseURL( new URL(request.url), imageConfig, + logger, ); if (!imageTransform) { @@ -166,6 +167,7 @@ export const GET: APIRoute = async ({ request }) => { inputBuffer, imageTransform, imageConfig, + logger, ); return new Response(new Uint8Array(data), { status: 200, @@ -185,7 +187,7 @@ export const GET: APIRoute = async ({ request }) => {

      -**Type:** (options: ImageTransform, imageConfig: AstroConfig['image']) => string | Promise\
      +**Type:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => string | Promise\

      @@ -195,57 +197,71 @@ For local services, this hook returns the URL of the endpoint that generates you For external services, this hook returns the final URL of the image. -For both types of services, `options` are the properties passed by the user as attributes of the `` component or as options to `getImage()`. +For both types of services, `options` are the properties passed by the user as attributes of the `` component or as options to `getImage()`. This hook also receives the image configuration and, since Astro v7.3.0, a logger. ### `parseURL()`

      -**Type:** (url: URL, imageConfig: AstroConfig['image']) => \{ src: string, [key: string]: any \} | undefined | Promise\<\{ src: string, [key: string]: any \}\> | Promise\
      +**Type:** (url: URL, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => \{ src: string, [key: string]: any \} | undefined | Promise\<\{ src: string, [key: string]: any \}\> | Promise\

      **Required for local services only; unavailable for external services** -This hook parses the generated URLs by `getURL()` back into an object with the different properties to be used by `transform` (for on-demand rendering and in dev mode). It is unused during build. +This hook parses the generated URLs by `getURL()` back into an object with the different properties to be used by `transform`. This receives three parameters: the URL to parse, the image configuration and, since Astro v7.3.0, a logger. + +This hook is used only for on-demand rendering and in development mode. It is unused during build. ### `transform()`

      -**Type:** (inputBuffer: Uint8Array, options: \{ src: string, [key: string]: any \}, imageConfig: AstroConfig['image']) => Promise\<\{ data: Uint8Array; format: ImageOutputFormat \}\>
      +**Type:** (inputBuffer: Uint8Array, options: \{ src: string, [key: string]: any \}, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Promise\<\{ data: Uint8Array; format: ImageOutputFormat \}\>

      **Required for local services only; unavailable for external services** -This hook transforms and returns the image and is called during the build to create the final asset files. +This hook transforms and returns the image and is called during the build to create the final asset files. This receives four parameters: the input image, an options object, the image configuration and, since Astro v7.3.0, a logger. + +You must return a `format` to ensure that the proper MIME type is served to users for on-demand rendering and development mode: + +```ts +import type { LocalImageService } from 'astro'; -You must return a `format` to ensure that the proper MIME type is served to users for on-demand rendering and development mode. +const service: LocalImageService = { + // ... + async transform(inputBuffer, transform, imageConfig, logger) { + logger.warn(`Could not optimize "${transform.src}". Passing it through unchanged.`); + return { data: inputBuffer, format: 'png' }; + }, +}; +``` ### `getHTMLAttributes()`

      -**Type:** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => Record\ | Promise\\>
      +**Type:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Record\ | Promise\\>

      **Optional for both local and external services** -This hook returns all additional attributes used to render the image as HTML, based on the parameters passed by the user (`options`). +This hook returns all additional attributes used to render the image as HTML, based on the parameters passed by the user (`options`). It also receives the image configuration and, since Astro v7.3.0, a logger. ### `getSrcSet()`

      -**Type:** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => UnresolvedSrcSetValue[] | Promise\
      +**Type:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => UnresolvedSrcSetValue[] | Promise\

      **Optional for both local and external services.** -This hook generates multiple variants of the specified image, for example, to generate a `srcset` attribute on an `` or ``'s `source`. +This hook generates multiple variants of the specified image, for example, to generate a `srcset` attribute on an `` or ``'s `source`. This receives three parameters: an options object, the image configuration and, since Astro v7.3.0, a logger. This hook returns an array of objects with the following properties: @@ -261,13 +277,13 @@ export type UnresolvedSrcSetValue = {

      -**Type:** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => ImageTransform | Promise\ +**Type:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => ImageTransform | Promise\

      **Optional for both local and external services** -This hook allows you to validate and augment the options passed by the user. This is useful for setting default options, or telling the user that a parameter is required. +This hook allows you to validate and augment the options passed by the user. This is useful for setting default options, or telling the user that a parameter is required. It also receives the image configuration and, since Astro v7.3.0, a logger you can use to warn the user about invalid options. [See how `validateOptions()` is used in Astro built-in services](https://github.com/withastro/astro/blob/0ab6bad7dffd413c975ab00e545f8bc150f6a92f/packages/astro/src/assets/services/service.ts#L124). @@ -275,13 +291,13 @@ This hook allows you to validate and augment the options passed by the user. Thi

      -**Type:** (url: string, imageConfig: AstroConfig['image'] ) => Omit\<ImageMetadata, 'src' | 'fsPath'\> | Promise\ImageMetadata, 'src' | 'fsPath'\>\> +**Type:** (url: string, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Omit\<ImageMetadata, 'src' | 'fsPath'\> | Promise\ImageMetadata, 'src' | 'fsPath'\>\>

      **Optional for both local and external services** -This hook allows you to extend the behavior of [`inferRemoteSize()`](/en/reference/modules/astro-assets/#inferremotesize). This is useful for reducing network traffic by caching images, or when you can predict image information from the image URL. +This hook allows you to extend the behavior of [`inferRemoteSize()`](/en/reference/modules/astro-assets/#inferremotesize). This is useful for reducing network traffic by caching images, or when you can predict image information from the image URL. This receives three parameters: the image URL, the image configuration and, since Astro v7.3.0, a logger. ## User configuration diff --git a/src/content/docs/en/reference/modules/astro-assets.mdx b/src/content/docs/en/reference/modules/astro-assets.mdx index 774cb2e15bd18..52de5ee406d98 100644 --- a/src/content/docs/en/reference/modules/astro-assets.mdx +++ b/src/content/docs/en/reference/modules/astro-assets.mdx @@ -453,6 +453,11 @@ The background color to use when flattening an image to transform it into the re By default, Sharp uses a black background when flattening an image. Specifying a different background color is especially useful when transforming images with transparent backgrounds to a format that does not support transparency (e.g. `.jpeg`): ```astro title="src/components/MyComponent.astro" "background" +--- +import { Image } from 'astro:assets'; +import myImage from '../assets/my_image.png'; +--- + A description of my image` component. -This takes an options object with the [same properties as the Image component](#image-) (except `alt`) and returns a [`GetImageResult` object](#getimageresult). +This takes an options object with the [same properties as the Image component](#image-) (except `alt` and `sizes`) and returns a [`GetImageResult` object](#getimageresult). The following example generates an AVIF `background-image` for a `
      `: @@ -739,12 +744,13 @@ When called on a route [rendered on-demand](/en/guides/on-demand-rendering/), th ```ts "context.url" import type { APIRoute } from "astro"; -import { fontData, experimental_getFontFileURL } from "astro:assets" +import { fontData, experimental_getFontFileURL } from "astro:assets"; export const prerender = false; // Not needed in 'server' mode export const GET: APIRoute = async (context) => { // ... + const fontPath = fontData["--font-roboto"][0]?.src[0]?.url; const url = experimental_getFontFileURL(fontPath, context.url); // ... }; @@ -859,11 +865,13 @@ The following example reuses the `baseService` to create a new image service: import { baseService } from "astro/assets"; const newImageService = { - getURL: baseService.getURL, - parseURL: baseService.parseURL, - getHTMLAttributes: baseService.getHTMLAttributes, - async transform(inputBuffer, transformOptions) {...} -} + getURL: baseService.getURL, + parseURL: baseService.parseURL, + getHTMLAttributes: baseService.getHTMLAttributes, + async transform(inputBuffer, transformOptions) { + /* ... */ + }, +}; ``` ### `getConfiguredImageService()` @@ -1423,7 +1431,9 @@ A value ready to use in the `srcset` attribute. **Type:** `object`

      -Defines the options accepted by the image transformation service. This contains a required `src` property, optional predefined properties, and any additional properties required by the image service: +Defines the options accepted by the image transformation service. This contains a required `src` property, optional predefined properties, and any additional properties required by the image service. + +The predefined properties match those accepted by the [`` component](#image-), except for `alt` and `sizes`. The following properties use different types. #### `ImageTransform.src` @@ -1452,64 +1462,6 @@ The width of the image. The height of the image. -#### `ImageTransform.widths` - -

      - -**Type:** `number[] | undefined`
      - -

      - -A list of widths to generate for the image. - -#### `ImageTransform.densities` - -

      - -**Type:** ``(number | `${number}x`)[] | undefined``
      - -

      - -A list of pixel densities to generate for the image. - -#### `ImageTransform.quality` - -

      - -**Type:** ImageQuality | undefined -

      - -The desired quality for the output image. - -#### `ImageTransform.format` - -

      - -**Type:** ImageOutputFormat | undefined -

      - -The desired format for the output image. - -#### `ImageTransform.fit` - -

      - -**Type:** `'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | string | undefined`
      - -

      - -Defines a list of allowed values for the `object-fit` CSS property, extensible with any string. - -#### `ImageTransform.position` - -

      - -**Type:** `string | undefined`
      - -

      - -Controls the value for the `object-position` CSS property. - ### `UnresolvedImageTransform`

      diff --git a/src/content/docs/en/reference/renderer-reference.mdx b/src/content/docs/en/reference/renderer-reference.mdx index ac0b478b38163..a52142f7c8142 100644 --- a/src/content/docs/en/reference/renderer-reference.mdx +++ b/src/content/docs/en/reference/renderer-reference.mdx @@ -151,8 +151,16 @@ Defines the [client directive](/en/reference/directives-reference/#client-direct Renderers can use this value to conditionally include client-side hydration state. For example, a renderer can skip serializing transfer state for components that will not be hydrated: -```ts -async function renderToStaticMarkup(Component, props, children, metadata) { +```ts title="my-renderer/server.ts" +import type { AstroComponentMetadata } from 'astro'; +import { render } from './custom-render'; + +async function renderToStaticMarkup( + Component: any, + props: Record, + slots: Record, + metadata?: AstroComponentMetadata, +) { const willHydrate = !!metadata?.hydrate; // Skip serializing hydration state if the component won't be hydrated return render(Component, props, { includeTransferState: willHydrate }); diff --git a/src/content/docs/en/reference/routing-reference.mdx b/src/content/docs/en/reference/routing-reference.mdx index df0d1ccf64a2f..c48b3f242063e 100644 --- a/src/content/docs/en/reference/routing-reference.mdx +++ b/src/content/docs/en/reference/routing-reference.mdx @@ -247,7 +247,7 @@ const { page } = Astro.props; - `pageSize` - The number of items shown per page (`10` by default) - `params` - Send additional parameters for creating dynamic routes - `props` - Send additional props to be available on each page - - `format` - **Since v7.1.0.** A function that allows to manipulate the computed URLs before being rendered. + - `format` - **Since v7.1.0.** A function that allows you to manipulate the computed URLs before being rendered. `paginate()` assumes a file name of `[page].astro` or `[...page].astro`. The `page` param becomes the page number in your URL: diff --git a/src/content/docs/es/basics/astro-components.mdx b/src/content/docs/es/basics/astro-components.mdx index 8168bb4164ea5..40101ef53e11f 100644 --- a/src/content/docs/es/basics/astro-components.mdx +++ b/src/content/docs/es/basics/astro-components.mdx @@ -18,7 +18,7 @@ Los componentes Astro son extremadamente flexibles. Un componente de Astro puede Lo más importante que hay que saber sobre los componentes de Astro es que **no se renderizan en el cliente**. Se renderizan en HTML en el momento de la compilación o bajo demanda. Puedes incluir código JavaScript dentro del frontmatter de tu componente, y todo será eliminado de la página final enviada a los navegadores de tus usuarios. El resultado es un sitio más rápido, sin ninguna huella de JavaScript añadida por defecto. -Cuando tu componente Astro necesite interactividad en el lado del cliente, puedes añadir [etiquetas HTML estándar ` + + + +

      + + +

      + Lorem ipsum +

      +
      + + +``` + +## Intellisense para TypeScript + +La integración `@astrojs/alpine` añade `Alpine` al [objeto global window](/es/guides/typescript/#window-y-globalthis). Para el autocompletado del IDE, añade lo siguiente a tu `src/env.d.ts`: + +```ts title="src/env.d.ts" +interface Window { + Alpine: import('alpinejs').Alpine; +} +``` + +## Ejemplos + +* El [ejemplo de Astro y Alpine.js](https://github.com/withastro/astro/tree/main/examples/framework-alpine) muestra cómo usar Alpine.js en un proyecto de Astro. + +[astro-integration]: /es/guides/integrations/ + +[astro-ui-frameworks]: /es/guides/framework-components/#uso-de-componentes-de-frameworks diff --git a/src/content/docs/es/guides/integrations-guide/db.mdx b/src/content/docs/es/guides/integrations-guide/db.mdx new file mode 100644 index 0000000000000..bd6b372853dbd --- /dev/null +++ b/src/content/docs/es/guides/integrations-guide/db.mdx @@ -0,0 +1,11 @@ +--- +title: '@astrojs/db' +description: Aprende a usar la integración @astrojs/db en tu proyecto de Astro. +i18nReady: true +--- + +:::caution[Eliminado] +La integración de Astro DB fue declarada obsoleta en la v6.4 y ha sido eliminada desde Astro v7.0. + +Si estabas usando esta integración en tu proyecto, te recomendamos migrar a una biblioteca de terceros para la funcionalidad de base de datos. Consulta la [guía de migración a Astro v7.0](/es/guides/upgrade-to/v7/#removed-astrojsdb) para obtener más detalles y recomendaciones sobre cómo actualizar tu proyecto. +::: diff --git a/src/content/docs/es/guides/integrations-guide/deno.mdx b/src/content/docs/es/guides/integrations-guide/deno.mdx new file mode 100644 index 0000000000000..f2c2d11f25cb5 --- /dev/null +++ b/src/content/docs/es/guides/integrations-guide/deno.mdx @@ -0,0 +1,13 @@ +--- +title: '@deno/astro-adapter' +description: El adaptador de Deno para Astro +sidebar: + label: Deno +i18nReady: true +--- + +El adaptador de Deno permite a Astro desplegar tu sitio SSR en entornos Deno, incluyendo Deno Deploy. + +El adaptador de Deno era mantenido anteriormente por Astro, pero ahora es mantenido directamente por Deno. Su uso está documentado actualmente [en el repositorio del adaptador de Deno](https://github.com/denoland/deno-astro-adapter). + +Si actualmente estás utilizando este adaptador de Astro, necesitarás migrar a la nueva versión de Deno o [añadir otro adaptador](/es/guides/on-demand-rendering/) para continuar usando SSR en tu proyecto. diff --git a/src/content/docs/es/guides/integrations-guide/preact.mdx b/src/content/docs/es/guides/integrations-guide/preact.mdx new file mode 100644 index 0000000000000..ed0063690a899 --- /dev/null +++ b/src/content/docs/es/guides/integrations-guide/preact.mdx @@ -0,0 +1,267 @@ +--- +type: integration +title: '@astrojs/preact' +description: Aprende a usar la integración del framework @astrojs/preact para extender el soporte de componentes en tu proyecto de Astro. +sidebar: + label: Preact +githubIntegrationURL: 'https://github.com/withastro/astro/tree/main/packages/integrations/preact/' +category: renderer +i18nReady: true +--- + +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro' +import Since from '~/components/Since.astro'; + +{/* TODO: add link to /es/guides/integrations/ */} +Esta **integración de Astro** permite el renderizado y la hidratación del lado del cliente para tus componentes de [Preact](https://preactjs.com/). + +## ¿Por qué Preact? + +Preact es una biblioteca que te permite construir componentes de UI interactivos para la web. Si deseas crear funciones interactivas en tu sitio usando JavaScript, es posible que prefieras usar su formato de componentes en lugar de utilizar las API del navegador directamente. + +Preact también es una excelente opción si has utilizado React anteriormente. Preact proporciona la misma API que React, pero en un paquete mucho más pequeño de 3kB. Incluso admite la renderización de muchos componentes de React mediante la opción de configuración `compat` (ver a continuación). + +**¿Quieres aprender más sobre Preact antes de usar esta integración?**\ +Echa un vistazo a [“Aprende Preact”](https://preactjs.com/tutorial), un tutorial interactivo en su sitio web. + +## Instalación + +Astro incluye un comando `astro add` para automatizar la configuración de las integraciones oficiales. Si lo prefieres, puedes [instalar las integraciones manualmente](#instalación-manual) en su lugar. + +Para instalar `@astrojs/preact`, ejecuta lo siguiente desde el directorio de tu proyecto y sigue las instrucciones: + + + + ```sh + npx astro add preact + ``` + + + ```sh + pnpm astro add preact + ``` + + + ```sh + yarn astro add preact + ``` + + + +Si tienes algún problema, [no dudes en reportarlo en GitHub](https://github.com/withastro/astro/issues) e intenta seguir los pasos de instalación manual que aparecen a continuación. + +### Instalación manual + +Primero, instala el paquete `@astrojs/preact`: + + + + ```sh + npm install @astrojs/preact + ``` + + + ```sh + pnpm add @astrojs/preact + ``` + + + ```sh + yarn add @astrojs/preact + ``` + + + +La mayoría de los gestores de paquetes también instalarán las dependencias de pares asociadas. Si ves una advertencia como `Cannot find package 'preact'` (o similar) al iniciar Astro, tendrás que instalar Preact: + + + + ```sh + npm install preact + ``` + + + ```sh + pnpm add preact + ``` + + + ```sh + yarn add preact + ``` + + + +Luego, aplica la integración a tu archivo `astro.config.*` usando la propiedad `integrations`: + +```js title="astro.config.mjs" ins={2} ins="preact()" +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; + +export default defineConfig({ + // ... + integrations: [preact()], +}); +``` + +Y agrega el siguiente código al archivo `tsconfig.json`. + +```json title="tsconfig.json" ins={5-8} +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"], + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} +``` + +## Uso + +Para usar tu primer componente de Preact en Astro, dirígete a nuestra [documentación de frameworks de UI][astro-ui-frameworks]. Explorarás: + +* 📦 cómo se cargan los componentes del framework, +* 💧 opciones de hidratación del lado del cliente y +* 🤝 oportunidades para mezclar y anidar frameworks + +{/* TODO: add link to /es/guides/integrations/ */} +También consulta nuestra Documentación de integraciones de Astro para obtener más información sobre las integraciones. + +## Configuración + +La integración de Preact en Astro maneja cómo se renderizan los componentes de Preact y tiene sus propias opciones. Cámbialas en el archivo `astro.config.mjs` que es donde residen los ajustes de integración de tu proyecto. + +Para un uso básico, no necesitas configurar la integración de Preact. + +### `compat` + +

      + +**Tipo:** `boolean`
      + +

      + +Puedes habilitar `preact/compat`, la capa de compatibilidad de Preact para renderizar componentes de React sin necesidad de instalar o enviar las bibliotecas más pesadas de React a los navegadores web de tus usuarios. + +Para hacerlo, pasa un objeto a la integración de Preact y establece `compat: true`. + +```js title="astro.config.mjs" "compat: true" +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; + +export default defineConfig({ + integrations: [preact({ compat: true })], +}); +``` + +Con la opción `compat` habilitada, la integración de Preact renderizará tanto componentes de React como componentes de Preact en tu proyecto y también te permitirá importar componentes de React dentro de componentes de Preact. Lee más en [“Cambiarse a Preact (desde React)”](https://preactjs.com/guide/v10/switching-to-preact) en el sitio web de Preact. + +Al importar bibliotecas de componentes de React, para intercambiar las dependencias de `react` y `react-dom` por `preact/compat`, puedes usar [`overrides`](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides) para lograrlo. + +```json title="package.json" +{ + "overrides": { + "react": "npm:@preact/compat@latest", + "react-dom": "npm:@preact/compat@latest" + } +} +``` + +Consulta la documentación de [overrides de `pnpm`](https://pnpm.io/package_json#pnpmoverrides) y [resolutions de `yarn`](https://yarnpkg.com/configuration/manifest#resolutions) para sus respectivas características de sobrescritura. + +:::note +Actualmente, la opción `compat` solo funciona para las bibliotecas de React que exportan código como ESM. Si ocurre un error durante el tiempo de compilación, intenta agregar la biblioteca a `vite.ssr.noExternal: ['the-react-library']` en tu archivo `astro.config.mjs`. +::: + +### `babel` + +

      + +**Tipo:** [`BabelOptions`](https://github.com/preactjs/preset-vite#babel-configuration)
      + +

      + +Puedes pasar [opciones de configuración de Babel](https://babeljs.io/docs/options) adicionales al plugin de Vite para Preact. Esto te permite personalizar la transformación de Babel aplicada a tus componentes de Preact. + +Por ejemplo, la siguiente configuración le indica a Babel que cargue `.babelrc` al procesar tus componentes de Preact: + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; + +export default defineConfig({ + integrations: [ + preact({ + babel: { + babelrc: true, + }, + }), + ], +}); +``` + +### `devtools` + +

      + +**Tipo:** `boolean`
      + +

      + +Puedes habilitar las [devtools de Preact](https://preactjs.github.io/preact-devtools/) en desarrollo pasando un objeto con `devtools: true` a la configuración de tu integración `preact()`: + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; + +export default defineConfig({ + // ... + integrations: [preact({ devtools: true })], +}); +``` + +## Opciones + +### Combinando múltiples frameworks JSX + +Cuando utilizas múltiples frameworks JSX (React, Preact, Solid) en el mismo proyecto, Astro necesita determinar qué transformaciones específicas de cada framework JSX deben usarse para cada uno de tus componentes. Si solo has añadido la integración de un framework JSX a tu proyecto, no se necesita configuración adicional. + +Usa las opciones de configuración `include` (requerido) y `exclude` (opcional) para especificar qué archivos pertenecen a qué framework. Proporciona un array de archivos y/o carpetas en `include` para cada framework que estés utilizando. Se pueden usar comodines para incluir múltiples rutas de archivos. + +Recomendamos colocar los componentes de un mismo framework en la misma carpeta (p. ej. `/components/react/` y `/components/solid/`) para que sea más fácil especificar tus inclusiones, pero esto no es obligatorio: + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; +import react from '@astrojs/react'; +import svelte from '@astrojs/svelte'; +import vue from '@astrojs/vue'; +import solid from '@astrojs/solid-js'; + +export default defineConfig({ + // Habilita varios frameworks para soportar todo tipo de componentes. + // ¡No se necesita `include` si solo estás usando un único framework JSX! + integrations: [ + preact({ + include: ['**/preact/*'], + }), + react({ + include: ['**/react/*'], + }), + solid({ + include: ['**/solid/*'], + }), + ], +}); +``` + +## Ejemplos + +* El [ejemplo de Astro Preact](https://github.com/withastro/astro/tree/latest/examples/framework-preact) muestra cómo usar un componente interactivo de Preact en un proyecto de Astro. +* El [ejemplo de Astro Nanostores](https://github.com/withastro/astro/tree/latest/examples/with-nanostores) muestra cómo compartir el estado entre diferentes componentes — ¡e incluso entre diferentes frameworks! — en un proyecto de Astro. + +[astro-integration]: /en/guides/integrations/ + +[astro-ui-frameworks]: /es/guides/framework-components/#uso-de-componentes-de-frameworks diff --git a/src/content/docs/es/guides/integrations-guide/prefetch.mdx b/src/content/docs/es/guides/integrations-guide/prefetch.mdx new file mode 100644 index 0000000000000..84844856e8943 --- /dev/null +++ b/src/content/docs/es/guides/integrations-guide/prefetch.mdx @@ -0,0 +1,13 @@ +--- +title: '@astrojs/prefetch' +description: La integración obsoleta de prefetch. +sidebar: + label: Prefetch +i18nReady: true +--- + +:::caution[Eliminado] +`@astrojs/prefetch` ha sido reemplazado por la [funcionalidad `prefetch` integrada](/es/guides/prefetch/) introducida en Astro 3.5. Consulta la [guía de migración](/es/guides/prefetch/#migrando-desde-astrojsprefetch) para obtener instrucciones sobre cómo actualizar un proyecto más antiguo. + +Si todavía estás utilizando esta integración en un proyecto de Astro anterior a la versión 3.5, puedes leer una copia archivada del [README de `@astrojs/prefetch`](https://github.com/withastro/astro/blob/c47478bbf6b21973419f25234c68efb59466b368/packages%2Fintegrations%2Fprefetch%2FREADME.md) en GitHub. +::: diff --git a/src/content/docs/es/guides/integrations-guide/tailwind.mdx b/src/content/docs/es/guides/integrations-guide/tailwind.mdx new file mode 100644 index 0000000000000..6a02bce43469f --- /dev/null +++ b/src/content/docs/es/guides/integrations-guide/tailwind.mdx @@ -0,0 +1,11 @@ +--- +title: '@astrojs/tailwind' +description: Aprende a usar la integración @astrojs/tailwind en tu proyecto de Astro. +i18nReady: true +--- + +:::caution[Obsoleto] +Tailwind CSS ahora ofrece un plugin de Vite, que es la manera recomendada de usar Tailwind 4 en Astro. +::: + +Para usar Tailwind en Astro, sigue la [guía de estilos para Tailwind](/es/guides/styling/#tailwind). \ No newline at end of file diff --git a/src/content/docs/es/guides/integrations.mdx b/src/content/docs/es/guides/integrations.mdx new file mode 100644 index 0000000000000..b6eb0274d8d43 --- /dev/null +++ b/src/content/docs/es/guides/integrations.mdx @@ -0,0 +1,608 @@ +--- +title: Trabajando con integraciones +description: Aprende a añadir, configurar y crear integraciones para tu proyecto de Astro. +i18nReady: true +--- + +import IntegrationsNav from '~/components/IntegrationsNav.astro'; +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; +import { Steps } from '@astrojs/starlight/components'; +import { FileTree } from '@astrojs/starlight/components'; + +Las **integraciones de Astro** añaden nuevas funcionalidades y comportamientos a tu proyecto con solo unas pocas líneas de código. Puedes usar una integración oficial, [integraciones creadas por la comunidad](#encontrar-más-integraciones) o incluso [crear tu propia integración personalizada](#construir-tu-propia-integración). + +Las integraciones pueden… + +- Habilitar React, Vue, Svelte, Solid y otros frameworks populares de UI con un [renderizador](/es/guides/framework-components/). +- Habilitar el renderizado bajo demanda con un [adaptador SSR](/es/guides/on-demand-rendering/). +- Integrar herramientas como MDX y Partytown con unas pocas líneas de código. +- Añadir nuevas funciones a tu proyecto, como la generación automática de sitemaps. +- Escribir código personalizado que se enganche al proceso de compilación, al servidor de desarrollo y más. + +:::tip[Directorio de integraciones] +Explora o busca la colección completa de cientos de integraciones oficiales y de la comunidad en nuestro [directorio de integraciones](https://astro.build/integrations/). Encuentra paquetes para añadir a tu proyecto de Astro para autenticación, analíticas, rendimiento, SEO, accesibilidad, UI, herramientas de desarrollo y más. +::: + +## Integraciones oficiales + +Las siguientes integraciones son mantenidas por Astro. + + + +## Configuración automática de integraciones + +Astro incluye un comando `astro add` para automatizar la configuración de las integraciones oficiales. También se pueden añadir varios plugins de la comunidad usando este comando. Por favor, consulta la documentación propia de cada integración para comprobar si es compatible con `astro add`, o si debes [instalarla manualmente](#instalación-manual). + +Ejecuta el comando `astro add` utilizando el gestor de paquetes de tu preferencia y nuestro asistente automático de integraciones actualizará tu archivo de configuración e instalará cualquier dependencia necesaria. + + + + ```shell + npx astro add react + ``` + + + ```shell + pnpm astro add react + ``` + + + ```shell + yarn astro add react + ``` + + + +¡Incluso es posible añadir múltiples integraciones al mismo tiempo! + + + + ```shell + npx astro add react sitemap partytown + ``` + + + ```shell + pnpm astro add react sitemap partytown + ``` + + + ```shell + yarn astro add react sitemap partytown + ``` + + + +:::note[Manejo de dependencias de integración] +Si ves alguna advertencia como `Cannot find package '[package-name]'` después de añadir una integración, es posible que tu gestor de paquetes no haya instalado las [dependencias compartidas](https://nodejs.org/en/blog/npm/peer-dependencies/) por ti. Para instalar estos paquetes faltantes, ejecuta el siguiente comando: + + + + ```shell + npm install [nombre-del-paquete] + ``` + + + ```shell + pnpm add [nombre-del-paquete] + ``` + + + ```shell + yarn add [nombre-del-paquete] + ``` + + +::: + +### Instalación manual + +Las integraciones de Astro siempre se añaden a través de la propiedad `integrations` en tu archivo `astro.config.mjs`. + +Hay tres formas comunes de importar una integración a tu proyecto de Astro: + +1. [Instalar una integración desde un paquete npm](#instalar-un-paquete-npm). +2. Importar tu propia integración desde un archivo local dentro de tu proyecto. +3. Escribir tu integración en línea, directamente en tu archivo de configuración. + + ```js + // astro.config.mjs + import { defineConfig } from 'astro/config'; + import installedIntegration from '@astrojs/vue'; + import localIntegration from './my-integration.js'; + + export default defineConfig({ + integrations: [ + // 1. Importado desde un paquete npm instalado + installedIntegration(), + // 2. Importado desde un archivo JS local + localIntegration(), + // 3. Un objeto en línea + { name: 'namespace:id', hooks: { /* ... */ } }, + ] + }); + ``` + +{/*TODO: add link to /es/reference/integrations-reference/ */} +Consulta la referencia de la API de Integraciones para aprender todas las diferentes maneras en que puedes escribir una integración. + +#### Instalar un paquete npm + +Instala una integración de un paquete npm utilizando un gestor de paquetes y luego actualiza `astro.config.mjs` manualmente. + +Por ejemplo, para instalar la integración `@astrojs/sitemap`: + + +1. Instala la integración en las dependencias de tu proyecto utilizando tu gestor de paquetes preferido: + + + + ```shell + npm install @astrojs/sitemap + ``` + + + ```shell + pnpm add @astrojs/sitemap + ``` + + + ```shell + yarn add @astrojs/sitemap + ``` + + + +2. Importa la integración en tu archivo `astro.config.mjs` y añádela a tu array `integrations[]`, junto con cualquier opción de configuración: + + ```js title="astro.config.mjs" ins={2} ins="sitemap()" + import { defineConfig } from 'astro/config'; + import sitemap from '@astrojs/sitemap'; + + export default defineConfig({ + // ... + integrations: [sitemap()], + // ... + }); + ``` + + Ten en cuenta que diferentes integraciones pueden tener diferentes ajustes de configuración. Lee la documentación de cada integración y aplica cualquier opción de configuración necesaria a la integración que hayas elegido en `astro.config.mjs`. + + +### Opciones personalizadas + +Las integraciones casi siempre se crean como funciones factoría que devuelven el objeto de integración real. Esto te permite pasar argumentos y opciones a la función factoría para personalizar la integración según tu proyecto. + +```js +integrations: [ + // Ejemplo: Personaliza tu integración con argumentos de función + sitemap({ filter: true }) +] +``` + +### Alternar una integración + +Las integraciones falsy se ignoran, por lo que puedes activar y desactivar integraciones sin preocuparte por dejar valores booleanos o `undefined` restantes. + +```js +integrations: [ + // Ejemplo: Omite la construcción de un sitemap en Windows + process.platform !== 'win32' && sitemap() +] +``` + +## Actualizar integraciones + +Para actualizar todas las integraciones oficiales a la vez, ejecuta el comando `@astrojs/upgrade`. Esto actualizará tanto Astro como todas las integraciones oficiales a sus últimas versiones. + +### Actualización automática + + + + ```shell + # Actualizar Astro y las integraciones oficiales a la vez a la última versión + npx @astrojs/upgrade + ``` + + + ```shell + # Actualizar Astro y las integraciones oficiales a la vez a la última versión + pnpm dlx @astrojs/upgrade + ``` + + + ```shell + # Actualizar Astro y las integraciones oficiales a la vez a la última versión + yarn dlx @astrojs/upgrade + ``` + + + +### Actualización manual + +Para actualizar una o más integraciones manualmente, usa el comando adecuado para tu gestor de paquetes. + + + + ```shell + # Ejemplo: actualizar las integraciones de React y Partytown + npm install @astrojs/react@latest @astrojs/partytown@latest + ``` + + + ```shell + # Ejemplo: actualizar las integraciones de React y Partytown + pnpm add @astrojs/react@latest @astrojs/partytown@latest + ``` + + + ```shell + # Ejemplo: actualizar las integraciones de React y Partytown + yarn add @astrojs/react@latest @astrojs/partytown@latest + ``` + + + +## Eliminar una integración + + +1. Para eliminar una integración, primero desinstala la integración de tu proyecto. + + + + ```shell + npm uninstall @astrojs/react + ``` + + + ```shell + pnpm remove @astrojs/react + ``` + + + ```shell + yarn remove @astrojs/react + ``` + + + +2. A continuación, elimina la integración de tu archivo `astro.config.*`: + + ```js title="astro.config.mjs" del={2,6} + import { defineConfig } from 'astro/config'; + import react from '@astrojs/react'; + + export default defineConfig({ + integrations: [ + react() + ] + }); + ``` + + +## Encontrar más integraciones + +Puedes encontrar muchas integraciones desarrolladas por la comunidad en el [Directorio de Integraciones de Astro](https://astro.build/integrations/). Sigue los enlaces para obtener instrucciones detalladas de uso y configuración. + +## Construir tu propia integración + +La API de integraciones de Astro está inspirada en Rollup y Vite, y diseñada para resultar familiar a cualquiera que haya escrito antes un plugin de Rollup o Vite. + +{/*TODO: add link to /es/reference/integrations-reference/ */} +Consulta la referencia de la API de Integraciones para aprender qué pueden hacer las integraciones y cómo escribir una tú mismo. + +## Publicar tu integración en npm + +Publicar un componente de Astro es una excelente manera de reutilizar tu trabajo existente en tus proyectos y de compartirlo con la comunidad de Astro en general. Los componentes de Astro se pueden publicar directamente e instalar desde npm, al igual que cualquier otro paquete de JavaScript. + +¿Buscas inspiración? Revisa algunos de los [temas](https://astro.build/themes/) y [componentes](https://astro.build/integrations/) favoritos de la comunidad de Astro. También puedes [buscar en npm](https://www.npmjs.com/search?q=keywords:astro-component,withastro) para ver todo el catálogo público. + +:::tip[¿No quieres hacerlo solo?] +¡Revisa la [plantilla de componentes de la comunidad de Astro](https://github.com/Princesseuh/component-template) para obtener una plantilla lista para usar y respaldada por la comunidad! +::: + +### Inicio rápido + +Para empezar a desarrollar tu componente rápidamente, puedes usar una plantilla ya configurada. + + + + ```shell + # Inicializa la plantilla de componentes de Astro en un nuevo directorio + npm create astro@latest my-new-component-directory -- --template component + ``` + + + ```shell + # Inicializa la plantilla de componentes de Astro en un nuevo directorio + pnpm create astro@latest my-new-component-directory -- --template component + ``` + + + ```shell + # Inicializa la plantilla de componentes de Astro en un nuevo directorio + yarn create astro my-new-component-directory --template component + ``` + + + +### Crear un paquete + +:::note[Requisitos previos] +Antes de empezar, te será útil tener un conocimiento básico sobre: + +- [Módulos de Node](https://docs.npmjs.com/creating-node-js-modules) +- [Manifiesto del paquete (`package.json`)](https://docs.npmjs.com/creating-a-package-json-file) +- [Workspaces](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#workspaces) +::: + +Para crear un nuevo paquete, configura tu entorno de desarrollo para usar **workspaces** dentro de tu proyecto. Esto te permitirá desarrollar tu componente junto a una copia funcional de Astro. + + +- my-new-component-directory/ + - demo/ + - ... para pruebas y demostración + - package.json + - packages/ + - my-component/ + - index.js + - package.json + - ... archivos adicionales utilizados por el paquete + + +Este ejemplo, llamado `my-project`, crea un proyecto con un único paquete, llamado `my-component`, y un directorio `demo/` para probar y demostrar el componente. + +Esto se configura en el archivo `package.json` de la raíz del proyecto: + +```json +{ + "name": "my-project", + "workspaces": ["demo", "packages/*"] +} +``` + +En este ejemplo, se pueden desarrollar múltiples paquetes juntos desde el directorio `packages`. Estos paquetes también se pueden referenciar desde `demo`, donde puedes instalar una copia funcional de Astro. + + + + ```shell + npm create astro@latest demo -- --template minimal + ``` + + + ```shell + pnpm create astro@latest demo -- --template minimal + ``` + + + ```shell + yarn create astro demo --template minimal + ``` + + + +Hay dos archivos iniciales que conformarán tu paquete individual: `package.json` e `index.js`. + +#### `package.json` + +El `package.json` en el directorio del paquete incluye toda la información relacionada con tu paquete, incluyendo su descripción, dependencias y cualquier otro metadato del paquete. + +```json +{ + "name": "my-component", + "description": "Descripción del componente", + "version": "1.0.0", + "homepage": "https://github.com/owner/project#readme", + "type": "module", + "exports": { + ".": "./index.js", + "./astro": "./MyAstroComponent.astro", + "./react": "./MyReactComponent.jsx" + }, + "files": ["index.js", "MyAstroComponent.astro", "MyReactComponent.jsx"], + "keywords": ["astro-component", "withastro", "... etc", "... etc"] +} +``` + +##### `description` + +Una breve descripción de tu componente utilizada para ayudar a otros a saber qué hace. + +```json +{ + "description": "Un generador de elementos de Astro" +} +``` + +##### `type` + +El formato de módulo utilizado por Node.js y Astro para interpretar tus archivos `index.js`. + +```json +{ + "type": "module" +} +``` + +Usa `"type": "module"` para que tu `index.js` pueda usarse como punto de entrada con `import` y `export` . + +##### `homepage` + +La URL de la página principal del proyecto. + +```json +{ + "homepage": "https://github.com/owner/project#readme" +} +``` + +Esta es una excelente manera de dirigir a los usuarios a una demostración en línea, documentación o a la página principal de tu proyecto. + +##### `exports` + +Los puntos de entrada de un paquete cuando se importa por su nombre. + +```json +{ + "exports": { + ".": "./index.js", + "./astro": "./MyAstroComponent.astro", + "./react": "./MyReactComponent.jsx" + } +} +``` + +En este ejemplo, importar `my-component` usaría `index.js`, mientras que importar `my-component/astro` o `my-component/react` usaría `MyAstroComponent.astro` o `MyReactComponent.jsx` respectivamente. + +##### `files` + +Una optimización opcional para excluir archivos innecesarios del paquete enviado a los usuarios a través de npm. Ten en cuenta que **solo los archivos listados aquí se incluirán en tu paquete**, por lo que si agregas o cambias archivos necesarios para que tu paquete funcione, debes actualizar esta lista en consecuencia. + +```json +{ + "files": ["index.js", "MyAstroComponent.astro", "MyReactComponent.jsx"] +} +``` + +##### `keywords` + +Un array de palabras clave relevantes para tu componente, utilizado para ayudar a otros a [encontrar tu componente en npm](https://www.npmjs.com/search?q=keywords:astro-component,withastro) y en cualquier otro catálogo de búsqueda. + +Agrega `astro-component`, `astro-integration`, o `withastro` como una palabra clave especial para maximizar su visibilidad en el ecosistema de Astro. + +```json +{ + "keywords": ["astro-component", "withastro", "... etc", "... etc"] +} +``` + +:::tip +¡Nuestra [biblioteca de integraciones](https://astro.build/integrations/) también utiliza las palabras clave! [Consulta a continuación](#biblioteca-de-integraciones) la lista completa de palabras clave que buscamos en npm. +::: + +--- + +#### `index.js` + +El **punto de entrada principal del paquete** utilizado siempre que se importa tu paquete. + +```js +export { default as MyAstroComponent } from './MyAstroComponent.astro'; +export { default as MyReactComponent } from './MyReactComponent.jsx'; +``` + +Esto te permite empaquetar múltiples componentes juntos en una única interfaz. + +##### Ejemplo: Uso de importaciones con nombre + +```astro +--- +import { MyAstroComponent } from 'my-component'; +import { MyReactComponent } from 'my-component'; +--- + + +``` + +##### Ejemplo: Uso de importaciones de espacio de nombres + +```astro +--- +import * as Example from 'example-astro-component'; +--- + + +``` + +##### Ejemplo: Uso de importaciones individuales + +```astro +--- +import MyAstroComponent from 'example-astro-component/astro'; +import MyReactComponent from 'example-astro-component/react'; +--- + + +``` + +--- + +### Desarrollo de tu paquete + +Astro no tiene un "modo de paquete" dedicado para el desarrollo. En su lugar, debes usar un proyecto de demostración para desarrollar y probar tu paquete dentro de tu proyecto. Este puede ser un sitio web privado utilizado únicamente para desarrollo, o un sitio web público de demostración/documentación para tu paquete. + +Si estás extrayendo componentes de un proyecto existente, incluso puedes continuar usando ese proyecto para desarrollar tus componentes recién extraídos. + +### Probar tu componente + +Actualmente, Astro no incluye un ejecutor de pruebas. _(Si te interesa ayudar con esto, [¡únete a nosotros en Discord!](https://astro.build/chat))_ + +Mientras tanto, nuestra recomendación actual para las pruebas es: + + +1. Agrega un directorio de pruebas `fixtures` a tu directorio `demo/src/pages`. + +2. Agrega una nueva página por cada prueba que te gustaría ejecutar. + +3. Cada página debe incluir un uso diferente del componente que te gustaría probar. + +4. Ejecuta `astro build` para compilar tus fixtures y luego compara la salida del directorio `dist/__fixtures__/` con lo que esperabas. + + - my-project/demo/src/pages/\_\_fixtures\_\_/ + - test-name-01.astro + - test-name-02.astro + - test-name-03.astro + + + + +### Publicar tu componente + +Una vez que tengas tu paquete listo, puedes publicarlo en npm usando el comando `npm publish`. Si esto falla, asegúrate de haber iniciado sesión con `npm login` y de que tu archivo `package.json` esté correcto. Si se ejecuta con éxito, ¡ya terminaste! + +Ten en cuenta que no hubo un paso de `compilación` para los paquetes de Astro. Cualquier tipo de archivo que Astro admita de forma nativa, como `.astro`, `.ts`, `.jsx`, y `.css`, se puede publicar directamente sin un paso de compilación. + +Si necesitas utilizar otro tipo de archivo que Astro no admita de forma nativa, añade un paso de compilación a tu paquete. Este ejercicio avanzado corre por tu cuenta. + +### Biblioteca de integraciones + +¡Comparte tu gran trabajo añadiendo tu integración a nuestra [biblioteca de integraciones](https://astro.build/integrations/)! + +:::tip +¿Necesitas ayuda para desarrollar tu integración, o simplemente quieres conocer a otros creadores de integraciones? Tenemos un canal dedicado `#integrations` en nuestro [servidor de Discord](https://astro.build/chat). ¡Ven a saludar! +::: + +#### Datos de `package.json` + +La biblioteca se actualiza automáticamente cada semana, incorporando todos los paquetes publicados en npm con la palabra clave `astro-component`, `astro-integration`, o `withastro`. + +La biblioteca de integraciones lee los datos de `name`, `description`, `repository` y `homepage` desde tu archivo `package.json`. + +¡Los avatares son una excelente manera de destacar tu marca en la biblioteca! Una vez que tu paquete esté publicado, puedes [abrir una incidencia en GitHub](https://github.com/withastro/astro.build/issues/new/choose) con tu avatar adjunto y lo agregaremos a tu listado. + +:::tip +¿Necesitas sobrescribir la información que nuestra biblioteca lee de npm? ¡No hay problema! [Abre una incidencia](https://github.com/withastro/astro.build/issues/new/choose) con la información actualizada y nos aseguraremos de utilizar tu `name`, `description`, o `homepage` personalizado en su lugar. +::: + +#### Categorías + +Además de la palabra clave obligatoria `astro-component`, `astro-integration` o `withastro`, también se utilizan palabras clave especiales para organizar los paquetes de forma automática. Incluir cualquiera de las siguientes palabras clave añadirá tu integración a la categoría correspondiente en nuestra biblioteca de integraciones. + +| categoría | palabras clave | +|--------------------------------------- | -------------------------------------------- | +| Accesibilidad | `a11y`, `accessibility` | +| Adaptadores | `astro-adapter` | +| Analíticas | `analytics` | +| CSS + UI | `css`, `ui`, `icon`, `icons`, `renderer` | +| Frameworks | `renderer` | +| Cargadores de contenido | `astro-loader` | +| Imágenes + Multimedia | `media`, `image`, `images`, `video`, `audio` | +| Rendimiento + SEO | `performance`, `perf`, `seo`, `optimization` | +| Barra de herramientas de desarrollo | `devtools`, `dev-overlay`, `dev-toolbar` | +| Utilidades | `tooling`, `utils`, `utility` | + +Los paquetes que no incluyan ninguna palabra clave que coincida con una categoría se mostrarán como `Uncategorized`. + +### Compartir + +Te animamos a compartir tu trabajo, de verdad nos encanta ver lo que crean nuestros talentosos Astronautas. ¡Ven a compartir lo que has creado con nosotros en nuestro [Discord](https://astro.build/chat) o menciona a [@astrodotbuild](https://twitter.com/astrodotbuild) en un Tweet! diff --git a/src/content/docs/es/guides/on-demand-rendering.mdx b/src/content/docs/es/guides/on-demand-rendering.mdx index fc2005dd7c385..f82e99f6c0e4d 100644 --- a/src/content/docs/es/guides/on-demand-rendering.mdx +++ b/src/content/docs/es/guides/on-demand-rendering.mdx @@ -29,7 +29,7 @@ Astro mantiene adaptadores oficiales para [Node.js](https://nodejs.org/), [Netli ### Agrega un adaptador -Puedes agregar cualquiera de las [integraciones oficiales de adaptadores mantenidas por Astro](/es/guides/integrations/#official-integrations) con el siguiente comando `astro add`. Esto instalará el adaptador y realizará los cambios correspondientes en tu archivo `astro.config.mjs` en un solo paso. +Puedes agregar cualquiera de las [integraciones oficiales de adaptadores mantenidas por Astro](/es/guides/integrations/#integraciones-oficiales) con el siguiente comando `astro add`. Esto instalará el adaptador y realizará los cambios correspondientes en tu archivo `astro.config.mjs` en un solo paso. Por ejemplo, para instalar el adaptador de Netlify, ejecuta: @@ -51,7 +51,7 @@ Por ejemplo, para instalar el adaptador de Netlify, ejecuta: -También puedes [agregar un adaptador manualmente instalando el paquete de NPM](/es/guides/integrations/#installing-an-npm-package) (por ejemplo, `@astrojs/netlify`) y actualizando tu archivo `astro.config.mjs` por tu cuenta. +También puedes [agregar un adaptador manualmente instalando el paquete de NPM](/es/guides/integrations/#instalar-un-paquete-npm) (por ejemplo, `@astrojs/netlify`) y actualizando tu archivo `astro.config.mjs` por tu cuenta. Ten en cuenta que los diferentes adaptadores pueden tener configuraciones distintas. Lee la documentación de cada adaptador y aplica las opciones de configuración necesarias en `astro.config.mjs`. @@ -73,7 +73,7 @@ export const prerender = false Solo agrega una integración de adaptador para un entorno de servidor. ¡Todas las demás páginas se generan estáticamente en el momento de la compilación! --> - + ``` El siguiente ejemplo muestra cómo excluirse del prerenderizado para mostrar un número aleatorio cada vez que se accede al endpoint: @@ -106,7 +106,7 @@ export const prerender = true `output: 'server' `está configurado, ¡pero esta página es estática! ¡El resto de mi sitio se renderiza bajo demanda! --> - + ``` Agrega `export const prerender = true` a cualquier página o ruta para prerenderizar una página estática o endpoint: diff --git a/src/content/docs/es/reference/astro-syntax.mdx b/src/content/docs/es/reference/astro-syntax.mdx index 17131d12d7474..63b53864a81c0 100644 --- a/src/content/docs/es/reference/astro-syntax.mdx +++ b/src/content/docs/es/reference/astro-syntax.mdx @@ -115,7 +115,7 @@ Al usar etiquetas dinámicas: - **Los nombres de las variables deben ir en mayúsculas.** Por ejemplo, usa `Elemento`, no `elemento`. De lo contrario, Astro intentará representar el nombre de la variable como una etiqueta HTML literal. -- **No se admiten directivas de hidratación.** Cuando se usan las [directivas `client:*` de hidratación](/es/guides/framework-components/#hydrating-interactive-components), Astro necesita saber qué componentes empaquetar para producción, y el patrón de etiquetas dinámicas impide que esto funcione. +- **No se admiten directivas de hidratación.** Cuando se usan las [directivas `client:*` de hidratación](/es/guides/framework-components/#hidratación-de-componentes-interactivos), Astro necesita saber qué componentes empaquetar para producción, y el patrón de etiquetas dinámicas impide que esto funcione. - **La [directiva define:vars](/es/reference/directives-reference/#definevars) no está soportada.** Si no puedes envolver los hijos con un elemento adicional (por ejemplo, `
      `), entonces puedes añadir manualmente un ``style={`--myVar:${value}`}`` a tu Elemento. diff --git a/src/content/docs/es/reference/errors/client-address-not-available.mdx b/src/content/docs/es/reference/errors/client-address-not-available.mdx index ef4862d13a6c9..cd8e8ca1a44b1 100644 --- a/src/content/docs/es/reference/errors/client-address-not-available.mdx +++ b/src/content/docs/es/reference/errors/client-address-not-available.mdx @@ -12,5 +12,5 @@ Lamentablemente, el adaptador que estás utilizando no es compatible con `Astro. **Ver también:** -- [Integraciones oficiales](/es/guides/integrations/#official-integrations) +- [Integraciones oficiales](/es/guides/integrations/#integraciones-oficiales) - [Astro.clientAddress](/es/reference/api-reference/#clientaddress) diff --git a/src/content/docs/es/reference/errors/no-client-entrypoint.mdx b/src/content/docs/es/reference/errors/no-client-entrypoint.mdx index 9be65d979f4f7..fb0287175f6c6 100644 --- a/src/content/docs/es/reference/errors/no-client-entrypoint.mdx +++ b/src/content/docs/es/reference/errors/no-client-entrypoint.mdx @@ -18,4 +18,4 @@ Astro intentó hidratar un componente en el cliente, pero el renderizador utiliz **Ver también:** - [Opción addRenderer](/es/reference/integrations-reference/#addrenderer-option) -- [Hidratando componentes de framework](/es/guides/framework-components/#hydrating-interactive-components) +- [Hidratando componentes de framework](/es/guides/framework-components/#hidratación-de-componentes-interactivos) diff --git a/src/content/docs/es/reference/errors/no-matching-renderer.mdx b/src/content/docs/es/reference/errors/no-matching-renderer.mdx index 6c88341ca4649..4f7c4132b159b 100644 --- a/src/content/docs/es/reference/errors/no-matching-renderer.mdx +++ b/src/content/docs/es/reference/errors/no-matching-renderer.mdx @@ -20,4 +20,4 @@ Para archivos JSX/TSX, [@astrojs/react](/es/guides/integrations-guide/react/), [ **Ver también:** - [Componentes de framework](/es/guides/framework-components/) -- [Frameworks officiales](/es/guides/integrations/#official-integrations) +- [Frameworks oficiales](/es/guides/integrations/#integraciones-oficiales) diff --git a/src/content/docs/es/reference/modules/astro-hono.mdx b/src/content/docs/es/reference/modules/astro-hono.mdx new file mode 100644 index 0000000000000..9f1cc6ce10512 --- /dev/null +++ b/src/content/docs/es/reference/modules/astro-hono.mdx @@ -0,0 +1,36 @@ +--- +title: Referencia de la API de enrutamiento de Hono +sidebar: + label: 'astro/hono' +i18nReady: true +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 4 +--- +import ReadMore from '~/components/ReadMore.astro'; +import Since from '~/components/Since.astro'; + +

      + +El módulo `astro/hono` proporciona manejadores de [enrutamiento avanzado](/es/guides/routing/#enrutamiento-avanzado) construidos como envoltorios compatibles con Hono. + +## Importaciones de `astro/hono` + +```ts +import { + FetchState, + astro, + actions, + cache, + i18n, + middleware, + pages, + redirects, + sessions, + trailingSlash, +} from "astro/hono"; +``` + +El módulo `astro/hono` exporta los mismos nombres de manejadores que [`astro/fetch`](/es/reference/modules/astro-fetch/), pero cada uno devuelve una función de middleware de Hono. Esto te permite mezclar los manejadores de Astro con cualquier middleware de Hono del ecosistema. + +Aprende cómo [usar Hono](/es/guides/routing/#uso-con-hono) con Astro en la guía de enrutamiento avanzado. diff --git a/src/content/docs/es/tutorial/6-islands/1.mdx b/src/content/docs/es/tutorial/6-islands/1.mdx index 1fd4a76ab3c81..676436f108702 100644 --- a/src/content/docs/es/tutorial/6-islands/1.mdx +++ b/src/content/docs/es/tutorial/6-islands/1.mdx @@ -222,6 +222,6 @@ Para cada uno de los siguientes componentes, identifica lo que se enviará al na - [Guía de integraciones Astro](/es/guides/integrations/) -- [Uso de componentes de UI Framework en Astro](/es/guides/framework-components/#using-framework-components) +- [Uso de componentes de UI Framework en Astro](/es/guides/framework-components/#uso-de-componentes-de-frameworks) - [Referencia de las directivas del cliente Astro](/es/reference/directives-reference/#directivas-del-cliente) diff --git a/src/content/docs/fr/guides/authentication.mdx b/src/content/docs/fr/guides/authentication.mdx index 8f45e5aba0821..10cad95e1b77f 100644 --- a/src/content/docs/fr/guides/authentication.mdx +++ b/src/content/docs/fr/guides/authentication.mdx @@ -4,7 +4,7 @@ description: Une introduction à l'authentification avec Astro i18nReady: true --- -import { Steps } from '@astrojs/starlight/components' +import { Steps, Tabs, TabItem } from '@astrojs/starlight/components' import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro' import UIFrameworkTabs from '~/components/tabs/UIFrameworkTabs.astro' import ReadMore from '~/components/ReadMore.astro' @@ -66,12 +66,10 @@ Suivez le [guide Astro de Better Auth](https://www.better-auth.com/docs/integrat ### Utilisation -Better Auth propose un assistant `createAuthClient` pour divers frameworks, dont Vanilla JS, React, Vue, Svelte et Solid. +Better Auth propose un assistant `createAuthClient()` pour divers frameworks, dont Vanilla JS, React, Vue, Svelte et Solid. Par exemple, pour créer un client pour React, importez l'assistant depuis `'better-auth/react'` : - - ```ts title="src/lib/auth-client.ts" @@ -116,19 +114,26 @@ Une fois votre client configuré, vous pouvez l'utiliser pour authentifier les u ```astro title="src/pages/index.astro" --- -import Layout from 'src/layouts/Base.astro'; +import Layout from "../layouts/Base.astro"; --- + ``` @@ -137,12 +142,12 @@ Vous pouvez ensuite utiliser l'objet `auth` pour obtenir les données de session ```astro title="src/pages/index.astro" --- -import { auth } from "../../../lib/auth"; // importe votre instance Better Auth +import { auth } from "../lib/auth"; // importez votre instance Better Auth export const prerender = false; // Pas nécessaire en mode `server` - + const session = await auth.api.getSession({ - headers: Astro.request.headers, + headers: Astro.request.headers, }); --- @@ -152,9 +157,9 @@ const session = await auth.api.getSession({ Vous pouvez également utiliser l'objet `auth` pour protéger vos routes. L'exemple suivant utilise [le routage avancé d'Astro](/fr/guides/routing/#routage-avancé) avec [Hono](https://hono.dev/) pour exiger une session authentifiée pour chaque route sous `/dashboard`, en redirigeant vers la page d'accueil dans le cas contraire : ```ts title="src/fetch.ts" -import { Hono } from "hono"; +import { Hono, type Context, type Next } from "hono"; import { astro } from "astro/hono"; -import { auth } from "../auth"; // importez votre instance Better Auth +import { auth } from "./lib/auth"; // importez votre instance Better Auth const app = new Hono(); @@ -167,12 +172,14 @@ app.use(astro()); export default app; -async function requireAuth(c, next) { - const session = await auth.api.getSession({ headers: c.req.raw.headers }); - if (!session) { - return c.redirect("/"); - } - return next(); +async function requireAuth(c: Context, next: Next) { + const session = await auth.api.getSession({ + headers: c.req.raw.headers, + }); + if (!session) { + return c.redirect("/"); + } + return next(); } ``` @@ -225,38 +232,67 @@ Clerk fournit des composants qui vous permettent de contrôler la visibilité de ```astro title="src/pages/index.astro" --- -import Layout from 'src/layouts/Base.astro'; -import { SignedIn, SignedOut, UserButton, SignInButton } from '@clerk/astro/components'; +import Layout from "../layouts/Base.astro"; +import { Show, UserButton, SignInButton } from "@clerk/astro/components"; export const prerender = false; // Pas nécessaire en mode `server` --- - - - - - - + + + + + + ``` -Clerk vous permet également de protéger les routes sur le serveur à l'aide d'un middleware. Spécifiez les routes protégées et invitez les utilisateurs non authentifiés à se connecter : +Clerk vous permet également de protéger les routes sur le serveur à l'aide d'un middleware : -```ts title="src/middleware.ts" -import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'; + -const isProtectedRoute = createRouteMatcher([ - '/dashboard(.*)', - '/forum(.*)', -]); +1. Définissez `clerkMiddleware()` comme gestionnaire `onRequest` dans votre middleware : -export const onRequest = clerkMiddleware((auth, context) => { - if (!auth().userId && isProtectedRoute(context.request)) { - return auth().redirectToSignIn(); - } -}); -``` + ```ts title="src/middleware.ts" + import { clerkMiddleware } from "@clerk/astro/server"; + + export const onRequest = clerkMiddleware({ + /* options */ + }); + ``` + +2. Accédez à l'état d'authentification dans vos pages et vos routes d'API avec `locals.auth()`. Cela vous permet de vérifier si un utilisateur est authentifié et de prendre les mesures appropriées (par exemple, rediriger vers la page de connexion ou renvoyer une réponse différente). + + + + ```astro title="src/pages/dashboard.astro" + --- + const { isAuthenticated, redirectToSignIn } = Astro.locals.auth(); + + if (!isAuthenticated) return redirectToSignIn(); + --- + +

      Tableau de bord

      + ``` +
      + + ```ts title="src/pages/api/data.ts" + import type { APIRoute } from "astro"; + + export const GET: APIRoute = ({ locals }) => { + const { isAuthenticated, userId } = locals.auth(); + + if (!isAuthenticated) { + return new Response("Unauthorized", { status: 401 }); + } + + return Response.json({ userId }); + }; + ``` + +
      +
      ### Prochaines étapes @@ -286,7 +322,9 @@ export const onRequest = clerkMiddleware((auth, context) => { ## Scalekit -[Scalekit](https://scalekit.com/) est une plateforme d'authentification conçue pour les applications B2B et d'IA. Elle propose la connexion via les réseaux sociaux, l'authentification unique (SSO) d'entreprise, les liens magiques et bien plus encore, en gérant l'intégralité du flux OAuth 2.0 / OIDC afin que vous récupériez des jetons et un profil utilisateur sans avoir à créer d'interface utilisateur de connexion. Un seul environnement Scalekit prend en charge plusieurs applications, de sorte que les utilisateurs s'authentifient une seule fois et partagent la même session sur toutes vos plateformes (par exemple, `app.votreentreprise.com` et `doc.votreentreprise.com`). +[Scalekit](https://scalekit.com/) est une plateforme d'authentification pour les applications B2B et d'IA. Elle gère l'intégralité du flux OAuth 2.0 et OIDC, prenant en charge des méthodes telles que la connexion via les réseaux sociaux, l'authentification unique d'entreprise (SSO) et les liens magiques. Elle renvoie ensuite des jetons ainsi qu'un profil utilisateur sans nécessiter d'interface de connexion personnalisée. + +Un seul environnement Scalekit peut prendre en charge plusieurs applications. Cela vous permet de vous authentifier une seule fois et de partager la même session sur toutes vos plateformes (par exemple, `app.votreentreprise.com` et `doc.votreentreprise.com`). ### Guide diff --git a/src/content/docs/fr/guides/backend/firebase.mdx b/src/content/docs/fr/guides/backend/firebase.mdx index ae063352858d6..5b15cd468bdd8 100644 --- a/src/content/docs/fr/guides/backend/firebase.mdx +++ b/src/content/docs/fr/guides/backend/firebase.mdx @@ -233,6 +233,7 @@ export const GET: APIRoute = async ({ request, cookies, redirect }) => { cookies.set("__session", sessionCookie, { path: "/", + maxAge: fiveDays / 1000, }); return redirect("/dashboard"); diff --git a/src/content/docs/fr/guides/cms/emdash.mdx b/src/content/docs/fr/guides/cms/emdash.mdx index 26261ad9272fe..9c2d2eb6d8171 100644 --- a/src/content/docs/fr/guides/cms/emdash.mdx +++ b/src/content/docs/fr/guides/cms/emdash.mdx @@ -4,14 +4,235 @@ description: Ajouter du contenu à votre projet Astro en utilisant EmDash comme sidebar: label: EmDash type: cms -stub: true logo: emdash i18nReady: true --- +import { Steps } from '@astrojs/starlight/components'; +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; [EmDash](https://emdashcms.com/) est un CMS open-source full-stack conçu spécifiquement pour Astro, ajoutant du contenu reposant sur une base de données, une interface d'administration, une bibliothèque multimédia, des menus et des taxonomies à votre site. +:::tip +Pour démarrer un **nouveau projet Astro + EmDash à partir de zéro**, utilisez la CLI d'EmDash pour générer un projet préconfiguré : + + + + ```shell + npm create emdash@latest + ``` + + + ```shell + pnpm create emdash@latest + ``` + + + ```shell + yarn create emdash@latest + ``` + + +::: + +## Intégration avec Astro + +EmDash s'exécute au sein de votre projet Astro et s'appuie sur une base de données. Vous pouvez accéder à l'interface d'administration à l'adresse `/_emdash/admin` pour modifier le contenu. Vos pages l'afficheront en interrogeant la base de données à l'aide des [collections de contenu en direct](/fr/guides/content-collections/#collections-de-contenu-en-direct). + +Ce guide utilise l'[adaptateur Node.js](/fr/guides/integrations-guide/node/) et une base de données SQLite locale. Consultez la [documentation d'EmDash pour obtenir la liste des bases de données prises en charge](https://docs.emdashcms.com/deployment/database/). + +## Prérequis + +- Un projet Astro existant (Astro 6 ou une version ultérieure) configuré [avec un adaptateur](/fr/guides/on-demand-rendering/) et [`output: "server"`](/fr/reference/configuration-reference/#output). +- Node.js v22.16.0 ou version ultérieure. + +## Installation des dépendances + +React propulse l'interface d'administration d'EmDash et est une dépendance requise. Si votre projet n'utilise pas React, installez-le à l'aide de la commande `astro add` pour votre gestionnaire de paquets : + + + + ```shell + npx astro add react + ``` + + + ```shell + pnpm astro add react + ``` + + + ```shell + yarn astro add react + ``` + + + +Vous devez également installer le paquet EmDash : + + + + ```shell + npm install emdash + ``` + + + ```shell + pnpm add emdash + ``` + + + ```shell + yarn add emdash + ``` + + + +## Ajout de l'intégration + +Ajoutez l'intégration `emdash()` à votre fichier de configuration d'Astro, puis configurez une base de données ainsi qu'un backend de stockage multimédia : + +```js title="astro.config.mjs" ins={4-5, 12-18} +import { defineConfig } from "astro/config"; +import node from "@astrojs/node"; +import react from "@astrojs/react"; +import emdash, { local } from "emdash/astro"; +import { sqlite } from "emdash/db"; + +export default defineConfig({ + output: "server", + adapter: node({ mode: "standalone" }), + integrations: [ + react(), + emdash({ + database: sqlite({ url: "file:./data.db" }), + storage: local({ + directory: "./uploads", + baseUrl: "/_emdash/api/media/file", + }), + }), + ], +}); +``` + +## Ajout du chargeur de collections en direct + +Créez un fichier `src/live.config.ts` afin que la couche de contenu d'Astro puisse résoudre le contenu d'EmDash : + +```ts title="src/live.config.ts" +import { defineLiveCollection } from "astro:content"; +import { emdashLoader } from "emdash/runtime"; + +export const collections = { + _emdash: defineLiveCollection({ loader: emdashLoader() }), +}; +``` + +La collection `_emdash` redirige en interne vers vos types de contenu (par exemple, articles et pages). Toutes les collections existantes reposant sur des fichiers dans `src/content.config.ts` continuent de fonctionner parallèlement à celle-ci. + +## Exécution d'EmDash en local + +EmDash nécessite que vous complétiez l'assistant de configuration avant de tester vos propres pages. Tant que la configuration n'est pas terminée, aucun contenu n'est publié et les requêtes renvoient des résultats vides. + + +1. Démarrez le serveur de développement d'Astro pour initialiser la base de données et lancer l'interface d'administration : + + + + ```shell + npm run dev + ``` + + + ```shell + pnpm run dev + ``` + + + ```shell + yarn run dev + ``` + + + + Lors de la première exécution, EmDash crée `data.db` avec son schéma et deux collections par défaut : `pages` et `posts`. + +2. Rendez-vous sur `http://localhost:4321/_emdash/admin` dans le navigateur. EmDash vous redirige vers l'assistant de configuration. + +3. Dans l'étape **Site**, saisissez un titre de site et un slogan facultatif. + +4. Dans l'étape **Compte**, saisissez votre adresse e-mail et votre nom. Cela crée le compte administrateur. + +5. Dans l'étape **Se connecter**, sécurisez votre compte. Choisissez **Créer une clé d'accès** pour enregistrer une clé d'accès avec l'authentification biométrique de votre appareil, une clé de sécurité ou un code PIN. + +6. Connectez-vous avec votre nouvelle clé d'accès pour accéder au tableau de bord. + + +## Création de votre premier article + + +1. Dans le tableau de bord, cliquez sur le bouton **+ Post**. + +2. Ajoutez un titre et du contenu. EmDash stocke le texte enrichi au format [Portable Text](https://github.com/portabletext/portabletext), modifié dans un éditeur de blocs. Un slug d'URL est généré à partir du titre et peut être modifié dans la barre latérale. + +3. Cliquez sur **Enregistrer**, puis sur **Publier**. Seuls les articles publiés sont visibles par les visiteurs du site. + + +## Affichage du contenu EmDash + +Interrogez votre contenu avec `getEmDashCollection()` et `getEmDashEntry()`. Les deux suivent le modèle des collections en direct et renvoient les résultats au moment de la requête, de sorte que les modifications publiées apparaissent sans nouvelle compilation. + +### Affichage d'une liste d'articles + +L'exemple suivant affiche une liste de tous les titres d'articles publiés, chacun étant lié à une page d'article individuelle : + +```astro title="src/pages/blog.astro" +--- +import { getEmDashCollection } from "emdash"; + +const { entries: posts } = await getEmDashCollection("posts", { + status: "published", +}); +--- + +``` + +### Affichage d'un article individuel + +Pour afficher le contenu d'un article individuel, récupérez-le par son slug et affichez le contenu Portable Text avec le composant `` : + +```astro title="src/pages/posts/[...slug].astro" +--- +import { getEmDashEntry } from "emdash"; +import { PortableText } from "emdash/ui"; + +const { slug } = Astro.params; +const { entry: post } = await getEmDashEntry("posts", slug); + +if (!post) { + return Astro.redirect("/404"); +} +--- +
      +

      {post.data.title}

      + +
      +``` + +Consultez le [guide d'interrogation d'EmDash](https://docs.emdashcms.com/guides/querying-content/) pour plus d'informations sur le filtrage, la pagination, l'aperçu des brouillons et l'édition visuelle. + +## Déploiement d'EmDash + Astro + +EmDash se déploie en même temps que votre site sous la forme d'un projet Astro unique. Choisissez un hébergeur prenant en charge votre adaptateur, puis configurez une base de données de production et un espace de stockage multimédia. + +Consultez le [guide de déploiement d'EmDash pour Node.js](https://docs.emdashcms.com/deployment/nodejs/) et le [guide de déploiement d'EmDash pour Cloudflare](https://docs.emdashcms.com/deployment/cloudflare/) pour obtenir des instructions détaillées. Vous pouvez également consulter les [guides de déploiement](/fr/guides/deploy/) d'Astro et suivre les instructions pour effectuer le déploiement auprès de l'hébergeur de votre choix. + ## Ressources officielles -- [Documentation EmDash pour les développeurs Astro](https://docs.emdashcms.com/coming-from/astro/) +- [Documentation d'EmDash pour les développeurs Astro](https://docs.emdashcms.com/coming-from/astro/) - [EmDash sur GitHub](https://github.com/emdash-cms/emdash) diff --git a/src/content/docs/fr/guides/deploy/ishosting.mdx b/src/content/docs/fr/guides/deploy/ishosting.mdx new file mode 100644 index 0000000000000..7c7179330190c --- /dev/null +++ b/src/content/docs/fr/guides/deploy/ishosting.mdx @@ -0,0 +1,16 @@ +--- +title: Déployer votre site Astro sur is*hosting +description: Comment déployer votre site Astro sur le web en utilisant is*hosting +sidebar: + label: is*hosting +type: deploy +logo: ishosting +supports: ['ssr', 'static'] +i18nReady: true +--- + +[is\*hosting](https://ishosting.com/) est un fournisseur d'hébergement proposant des VPS et des serveurs dédiés dans plus de 40 emplacements, que vous pouvez utiliser pour auto-héberger un site Astro statique ou rendu côté serveur (SSR). + +## Ressources officielles + +- [Guide is\*hosting : déployer Astro sur un VPS (statique et SSR)](https://blog.ishosting.com/en/astro-on-vps) diff --git a/src/content/docs/fr/guides/integrations-guide/cloudflare.mdx b/src/content/docs/fr/guides/integrations-guide/cloudflare.mdx index 7af74195b2cd4..3dde384d7b76b 100644 --- a/src/content/docs/fr/guides/integrations-guide/cloudflare.mdx +++ b/src/content/docs/fr/guides/integrations-guide/cloudflare.mdx @@ -465,16 +465,20 @@ Lorsque vous utilisez ces gestionnaires dans le point d'entrée de votre worker, Pour une utilisation avec [`astro/fetch`](/fr/reference/modules/astro-fetch/). La fonction `cf()` importée depuis `@astrojs/cloudflare/fetch` reçoit un objet [`FetchState`](/fr/reference/modules/astro-fetch/#fetchstate), l'environnement Cloudflare (`env`) et le contexte d'exécution (`ExecutionContext`). Elle renvoie une réponse (`Response`) pour les accès aux ressources statiques, ou `undefined` lorsque la requête doit se poursuivre avec le rendu Astro : +

      + +Transmettez le même `FetchState` et la réponse de votre pipeline Astro à `finalize()` avant de la renvoyer. Cela applique à la réponse les cookies produits lors du rendu et les en-têtes de cache par défaut de l'adaptateur pour le CDN de Cloudflare. + ```ts title="src/worker.ts" import { astro, FetchState } from 'astro/fetch'; -import { cf } from '@astrojs/cloudflare/fetch'; +import { cf, finalize } from '@astrojs/cloudflare/fetch'; export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { const state = new FetchState(request); const asset = await cf(state, env, ctx); if (asset) return asset; - return astro(state); + return finalize(state, await astro(state)); }, }; ``` @@ -485,6 +489,8 @@ export default { Pour une utilisation avec [`astro/hono`](/fr/reference/modules/astro-hono/). La fonction `cf()` importée depuis `@astrojs/cloudflare/hono` renvoie un middleware Hono qui lit automatiquement `env` et `executionCtx` depuis le contexte Hono : +Dans la v14.3.0 et les versions ultérieures de `@astrojs/cloudflare`, ce middleware finalise également la réponse après l'exécution des gestionnaires Hono en aval. Les cookies produits lors du rendu et les en-têtes de cache par défaut de l'adaptateur pour le CDN de Cloudflare sont appliqués automatiquement. + ```ts title="src/worker.ts" import { Hono } from 'hono'; import { actions, middleware, pages, i18n } from 'astro/hono'; diff --git a/src/content/docs/fr/reference/cache-provider-reference.mdx b/src/content/docs/fr/reference/cache-provider-reference.mdx index c532beaa8b4d9..fb2c3cb8103f6 100644 --- a/src/content/docs/fr/reference/cache-provider-reference.mdx +++ b/src/content/docs/fr/reference/cache-provider-reference.mdx @@ -371,10 +371,45 @@ La requête (`request`) entrante est transmise en tant que second argument, ce q

      -**Type :** (context: \{ request: Request; url: URL; waitUntil?: (promise: Promise\) => void \}, next: MiddlewareNext) => Promise\ +**Type :** (context: \{ request: Request; url: URL; waitUntil?: (promise: Promise\) => void; logger: AstroRuntimeLogger \}, next: MiddlewareNext) => Promise\

      -Intercepte les requêtes pour mettre en œuvre la mise en cache à l'exécution. L'objet `context` inclut une fonction `waitUntil()` (lorsqu'elle est disponible dans l'environnement d'exécution) pour les tâches en arrière-plan, telles que la stratégie « stale-while-revalidate ». +Un hook optionnel qui intercepte une requête avant qu'Astro ne génère la route correspondante. Il reçoit un objet `context` en tant que premier argument et une fonction de rappel pour appeler le prochain middleware (`next()`) dans la chaîne. + +- Le contexte (`context`) contient les propriétés suivantes : +- `request` : l'objet [`Request`](https://developer.mozilla.org/fr/docs/Web/API/Request) entrant. +- `url` : une [URL](https://developer.mozilla.org/fr/docs/Web/API/URL) normalisée dérivée de la requête. +- `waitUntil() `: lorsqu'elle est disponible dans l'environnement d'exécution, une fonction pour définir des tâches en arrière-plan, telles que la revalidation d'une entrée de cache obsolète. +- `logger` : depuis Astro v7.3.0, une instance du [journaliseur (`logger`)](/fr/reference/api-reference/#logger) qui respecte la [destination de journalisation configurée](/fr/reference/configuration-reference/#options-du-journaliseur) + +L'exemple suivant implémente un hook `onRequest()` minimal qui journalise chaque URL ajoutée au cache : + +```ts title="mon-fournisseur/runtime.ts" ins={7-17} +import type { CacheProviderFactory } from 'astro'; + +const factory: CacheProviderFactory = (config) => { + const cache = new Map(); + return { + name: 'mon-fournisseur-cache', + async onRequest({ request, url, waitUntil, logger }, next) { + if (request.method !== 'GET') return next(); + + const cached = cache.get(url); + if (cached) return cached; + + const response = await next(); + cache.set(url, response.clone()); + logger.info(`Réponse mise en cache pour ${url}.`); + return response; + }, + async invalidate() { + // ... + }, + }; +}; + +export default factory; +``` #### `CacheProvider.invalidate()` diff --git a/src/content/docs/fr/reference/cli-reference.mdx b/src/content/docs/fr/reference/cli-reference.mdx index 5c2b434edaafd..434ad8128c46b 100644 --- a/src/content/docs/fr/reference/cli-reference.mdx +++ b/src/content/docs/fr/reference/cli-reference.mdx @@ -196,25 +196,7 @@ Les raccourcis clavier suivants peuvent être utilisés dans le terminal où le - `o + Entrée` pour ouvrir votre site Astro dans le navigateur. - `q + Entrée` pour quitter le serveur de développement. -

      Options

      - -

      - -La commande accepte [les options communes](#options-communes) et les options supplémentaires suivantes. - -#### `--ignore-lock` - -

      - -Démarre le serveur de développement sans vérifier ni écrire le fichier de verrouillage utilisé pour détecter les autres serveurs de développement en cours d'exécution. Cela permet de démarrer un nouveau serveur de développement parallèlement à un serveur déjà en cours d'exécution pour le même projet, au lieu de générer une erreur. - -```shell -astro dev --ignore-lock --port 4322 -``` - -Le nouveau serveur n'est pas suivi par les sous-commandes [`stop`, `status` ou `logs`](#sous-commandes-communes). - -Lorsqu'elle est combinée avec `--background` (y compris lorsqu'elle est déclenchée par un agent de codage IA) ou `--force`, une erreur est générée, car les deux dépendent du fichier de verrouillage. +La commande peut être combinée avec les [options communes](#options-communes) et les [sous-commandes communes](#sous-commandes-communes) pour contrôler davantage l'expérience de développement. ## `astro build` @@ -240,7 +222,7 @@ Les raccourcis clavier suivants peuvent être utilisés dans le terminal où le - `o` + `enter` pour ouvrir votre site Astro dans le navigateur. - `q` + `enter` pour quitter le serveur de prévisualisation. -La commande `astro preview` peut être combinée avec les [options communes](#options-communes) documentées ci-dessous pour contrôler davantage l'expérience de prévisualisation. Depuis la version v7.2.0, elle accepte également l'option [`--background`](#--background) ainsi que les sous-commandes [`stop`, `status` et `logs`](#sous-commandes-communes) pour gérer un serveur de prévisualisation s'exécutant en arrière-plan. +La commande peut être combinée avec les [options communes](#options-communes) et les [sous-commandes communes](#sous-commandes-communes) pour contrôler davantage l'expérience de prévisualisation. ## `astro check` @@ -560,6 +542,20 @@ astro dev --background --force Active [la journalisation au format JSON](/fr/reference/logger-reference/#loghandlersjson), ce qui est utile pour obtenir une sortie lisible par machine. +### `--ignore-lock` + +

      + +Empêche la vérification de l'existence d'un fichier de verrouillage et la nécessité d'en écrire un. Cela permet de démarrer un nouveau serveur de développement ou, depuis la v7.3.0, un serveur de prévisualisation parallèlement à un serveur déjà en cours d'exécution, au lieu de générer une erreur. + +```shell +astro dev --ignore-lock --port 4322 +``` + +Le nouveau serveur n'est pas suivi par les [sous-commandes communes](#sous-commandes-communes). + +Lorsqu'elle est combinée avec [`--background`](#--background) ou [`--force`](#--force-string), une erreur est générée, car les deux dépendent du fichier de verrouillage. + ## Options globales Utilisez ces options pour obtenir des informations à propos de la CLI `astro`. diff --git a/src/content/docs/fr/reference/errors/redirect-with-no-location.mdx b/src/content/docs/fr/reference/errors/redirect-with-no-location.mdx index 6b62284cf7c86..4512ea1bbfb3c 100644 --- a/src/content/docs/fr/reference/errors/redirect-with-no-location.mdx +++ b/src/content/docs/fr/reference/errors/redirect-with-no-location.mdx @@ -4,6 +4,8 @@ i18nReady: true githubURL: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts --- +> **RedirectWithNoLocation**: The redirect `Response` has no `Location` header. Use `Astro.redirect()` to create redirects, or add a `Location` header to the `Response`. + ## Qu'est-ce qui a mal tourné ? Une redirection doit recevoir un emplacement avec l'en-tête `Location`. diff --git a/src/content/docs/fr/reference/image-service-reference.mdx b/src/content/docs/fr/reference/image-service-reference.mdx index ba303a86a4930..c583f43b58305 100644 --- a/src/content/docs/fr/reference/image-service-reference.mdx +++ b/src/content/docs/fr/reference/image-service-reference.mdx @@ -31,21 +31,21 @@ Un service externe pointe vers une URL distante à utiliser comme attribut `src` import type { ExternalImageService, ImageTransform, AstroConfig } from "astro"; const service: ExternalImageService = { - validateOptions(options: ImageTransform, imageConfig: AstroConfig['image']) { + validateOptions(options: ImageTransform, imageConfig: AstroConfig['image'], logger) { const serviceConfig = imageConfig.service.config; // Appliquer la largeur maximale définie par l'utilisateur. if (options.width && options.width > serviceConfig.maxWidth) { - console.warn(`La largeur de l'image ${options.width} dépasse la largeur maximale ${serviceConfig.maxWidth}. Repli sur la largeur maximale.`); + logger.warn(`La largeur de l'image ${options.width} dépasse la largeur maximale ${serviceConfig.maxWidth}. Repli sur la largeur maximale.`); options.width = serviceConfig.maxWidth; } return options; }, - getURL(options, imageConfig) { + getURL(options, imageConfig, logger) { return `https://monsupercdn.com/${options.src}?q=${options.quality}&w=${options.width}&h=${options.height}`; }, - getHTMLAttributes(options, imageConfig) { + getHTMLAttributes(options, imageConfig, logger) { const { src, format, quality, ...attributes } = options; return { ...attributes, @@ -68,7 +68,7 @@ import type { ImageTransform, LocalImageService, AstroConfig } from "astro"; import { mySuperLibraryThatEncodesImages } from "@example/ma-super-bibliotheque"; const service: LocalImageService = { - getURL(options: ImageTransform, imageConfig) { + getURL(options: ImageTransform, imageConfig, logger) { const searchParams = new URLSearchParams(); searchParams.append('href', typeof options.src === "string" ? options.src : options.src.src); options.width && searchParams.append('w', options.width.toString()); @@ -79,7 +79,7 @@ const service: LocalImageService = { // Ou utilisez le point de terminaison intégré, qui appellera vos fonctions parseURL et transform : // return `/_image?${searchParams}`; }, - parseURL(url: URL, imageConfig) { + parseURL(url: URL, imageConfig, logger) { const params = url.searchParams; return { src: params.get('href')!, @@ -89,14 +89,14 @@ const service: LocalImageService = { quality: params.get('q'), }; }, - async transform(inputBuffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig) { + async transform(inputBuffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig, logger) { const { buffer } = await mySuperLibraryThatEncodesImages(options); return { data: buffer, format: options.format, }; }, - getHTMLAttributes(options, imageConfig) { + getHTMLAttributes(options, imageConfig, logger) { let targetWidth = options.width; let targetHeight = options.height; if (typeof options.src === "object") { @@ -141,7 +141,7 @@ import { getConfiguredImageService, imageConfig } from "astro:assets"; import * as mime from "mrmime"; import { getImageBuffer } from "./mon-recuperateur-d-images-personnalise.js"; -export const GET: APIRoute = async ({ request }) => { +export const GET: APIRoute = async ({ request, logger }) => { const imageService = await getConfiguredImageService(); if (!isLocalService(imageService)) { @@ -154,6 +154,7 @@ export const GET: APIRoute = async ({ request }) => { const imageTransform = await imageService.parseURL( new URL(request.url), imageConfig, + logger, ); if (!imageTransform) { @@ -166,6 +167,7 @@ export const GET: APIRoute = async ({ request }) => { inputBuffer, imageTransform, imageConfig, + logger, ); return new Response(new Uint8Array(data), { status: 200, @@ -185,7 +187,7 @@ export const GET: APIRoute = async ({ request }) => {

      -**Type :** (options: ImageTransform, imageConfig: AstroConfig['image']) => string | Promise\
      +**Type :** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => string | Promise\

      @@ -195,57 +197,71 @@ Pour les services locaux, ce hook renvoie l'URL du point de terminaison qui gén Pour les services externes, ce hook renvoie l'URL finale de l'image. -Pour les deux types de services, les `options` sont les propriétés passées par l'utilisateur comme attributs du composant `` ou comme options de `getImage()`. +Pour les deux types de services, les `options` sont les propriétés passées par l'utilisateur comme attributs du composant `` ou comme options de `getImage()`. Ce hook reçoit également la configuration de l'image et, depuis Astro v7.3.0, un journaliseur. ### `parseURL()`

      -**Type :** (url: URL, imageConfig: AstroConfig['image']) => \{ src: string, [key: string]: any \} | undefined | Promise\<\{ src: string, [key: string]: any \}\> | Promise\
      +**Type :** (url: URL, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => \{ src: string, [key: string]: any \} | undefined | Promise\<\{ src: string, [key: string]: any \}\> | Promise\

      **Requis pour les services locaux uniquement ; indisponible pour les services externes** -Ce hook analyse les URLs générées par `getURL()` en un objet avec les différentes propriétés à utiliser par `transform` (en SSR et en mode dev). Il n'est pas utilisé pendant la compilation. +Ce hook analyse les URLs générées par `getURL()` pour les transformer en un objet contenant les différentes propriétés à utiliser par `transform`. Il reçoit trois paramètres : l'URL à analyser, la configuration de l'image et, depuis Astro v7.3.0, un journaliseur. + +Ce hook est utilisé uniquement pour le rendu à la demande et en mode développement. Il n'est pas utilisé pendant la compilation. ### `transform()`

      -**Type :** (inputBuffer: Uint8Array, options: \{ src: string, [key: string]: any \}, imageConfig: AstroConfig['image']) => Promise\<\{ data: Uint8Array; format: ImageOutputFormat \}\>
      +**Type :** (inputBuffer: Uint8Array, options: \{ src: string, [key: string]: any \}, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Promise\<\{ data: Uint8Array; format: ImageOutputFormat \}\>

      **Requis pour les services locaux uniquement ; indisponible pour les services externes** -Ce hook transforme et renvoie l'image et est appelé pendant la compilation pour créer les fichiers de ressources finaux. +Ce hook transforme et renvoie l'image et est appelé pendant la compilation pour créer les fichiers de ressources finaux. Il reçoit quatre paramètres : l'image d'entrée, un objet d'options, la configuration de l'image et, depuis Astro v7.3.0, un journaliseur. Vous devez renvoyer un `format` pour garantir que le type MIME approprié est fourni aux utilisateurs pour le rendu à la demande et le mode de développement. +```ts +import type { LocalImageService } from 'astro'; + +const service: LocalImageService = { + // ... + async transform(inputBuffer, transform, imageConfig, logger) { + logger.warn(`Impossible d'optimiser « ${transform.src} ». Transmission sans modification.`); + return { data: inputBuffer, format: 'png' }; + }, +}; +``` + ### `getHTMLAttributes()`

      -**Type :** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => Record\ | Promise\\>
      +**Type :** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Record\ | Promise\\>

      **Facultatif pour les services locaux et externes** -Ce hook renvoie tous les attributs supplémentaires utilisés pour restituer l'image en HTML, en fonction des paramètres transmis par l'utilisateur (`options`). +Ce hook renvoie tous les attributs supplémentaires utilisés pour restituer l'image en HTML, en fonction des paramètres transmis par l'utilisateur (`options`). Il reçoit également la configuration de l'image et, depuis Astro v7.3.0, un journaliseur. ### `getSrcSet()`

      -**Type :** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => UnresolvedSrcSetValue[] | Promise\
      +**Type :** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => UnresolvedSrcSetValue[] | Promise\

      **Facultatif pour les services locaux et externes** -Ce hook génère plusieurs variantes de l'image spécifiée, par exemple, pour générer un attribut `srcset` sur une `` ou `source` sur ``. +Ce hook génère plusieurs variantes de l'image spécifiée, par exemple, pour générer un attribut `srcset` sur une `` ou `source` sur ``. Il reçoit trois paramètres : un objet d'options, la configuration de l'image et, depuis Astro v7.3.0, un journaliseur. Ce hook retourne un tableau d'objets avec les propriétés suivantes : @@ -261,13 +277,13 @@ export type UnresolvedSrcSetValue = {

      -**Type :** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => ImageTransform | Promise\ +**Type :** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => ImageTransform | Promise\

      **Facultatif pour les services locaux et externes** -Ce hook vous permet de valider et d'augmenter les options passées par l'utilisateur. C'est utile pour définir des options par défaut, ou pour indiquer à l'utilisateur qu'un paramètre est nécessaire. +Ce hook vous permet de valider et d'augmenter les options passées par l'utilisateur. C'est utile pour définir des options par défaut, ou pour indiquer à l'utilisateur qu'un paramètre est nécessaire. Il reçoit également la configuration de l'image et, depuis Astro v7.3.0, un journaliseur que vous pouvez utiliser pour avertir l'utilisateur des options invalides. [Voir comment `validateOptions()` est utilisé dans les services intégrés d'Astro](https://github.com/withastro/astro/blob/0ab6bad7dffd413c975ab00e545f8bc150f6a92f/packages/astro/src/assets/services/service.ts#L124). @@ -275,13 +291,13 @@ Ce hook vous permet de valider et d'augmenter les options passées par l'utilisa

      -**Type :** (url: string, imageConfig: AstroConfig['image'] ) => Omit\<ImageMetadata, 'src' | 'fsPath'\> | Promise\ImageMetadata, 'src' | 'fsPath'\>\> +**Type :** (url: string, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Omit\<ImageMetadata, 'src' | 'fsPath'\> | Promise\ImageMetadata, 'src' | 'fsPath'\>\>

      **Facultatif pour les services locaux et externes** -Ce hook vous permet d'étendre le comportement de [`inferRemoteSize()`](/fr/reference/modules/astro-assets/#inferremotesize). Ceci est utile pour réduire le trafic réseau en mettant les images en cache, ou lorsque vous pouvez prédire les informations d'une image à partir de son URL. +Ce hook vous permet d'étendre le comportement de [`inferRemoteSize()`](/fr/reference/modules/astro-assets/#inferremotesize). Ceci est utile pour réduire le trafic réseau en mettant les images en cache, ou lorsque vous pouvez prédire les informations d'une image à partir de son URL. Il reçoit trois paramètres : l'URL de l'image, la configuration de l'image et, depuis Astro v7.3.0, un journaliseur. ## Configuration de l'utilisateur diff --git a/src/content/docs/fr/reference/modules/astro-assets.mdx b/src/content/docs/fr/reference/modules/astro-assets.mdx index daca5477511aa..d9c5c06e8960a 100644 --- a/src/content/docs/fr/reference/modules/astro-assets.mdx +++ b/src/content/docs/fr/reference/modules/astro-assets.mdx @@ -646,7 +646,7 @@ Si vous avez besoin de l'URL de l'image résultante côté client, vous pouvez [ La fonction `getImage()` est prévue pour générer des images destinées à être utilisées ailleurs que directement en HTML, par exemple dans une [route d'API](/fr/guides/endpoints/#points-de-terminaison-du-serveur-routes-api). Elle vous permet également de créer votre propre composant `` personnalisé. -Cette fonction prend un objet d'options avec les [mêmes propriétés que le composant Image](#image-) (sauf `alt`) et renvoie un [objet `GetImageResult`](#getimageresult). +Cette fonction prend un objet d'options avec les [mêmes propriétés que le composant Image](#image-) (sauf `alt` et `sizes`) et renvoie un [objet `GetImageResult`](#getimageresult). L'exemple suivant génère un arrière-plan (`background-image`) au format AVIF pour un élément `
      ` : @@ -1423,7 +1423,9 @@ Une valeur prête à être utilisée dans l'attribut `srcset`. **Type :** `object`

      -Définit les options acceptées par le service de transformation d'images. Ceci contient une propriété `src` obligatoire, des propriétés prédéfinies facultatives et toutes les propriétés supplémentaires requises par le service d'images : +Définit les options acceptées par le service de transformation d'images. Ceci contient une propriété `src` obligatoire, des propriétés prédéfinies facultatives et toutes les propriétés supplémentaires requises par le service d'images. + +Les propriétés prédéfinies correspondent à celles acceptées par le [composant ``](#image-), à l'exception de `alt` et `sizes`. Les propriétés suivantes utilisent des types différents. #### `ImageTransform.src` @@ -1452,64 +1454,6 @@ La largeur de l'image. La hauteur de l'image. -#### `ImageTransform.widths` - -

      - -**Type :** `number[] | undefined`
      - -

      - -Une liste de largeurs à générer pour l'image. - -#### `ImageTransform.densities` - -

      - -**Type :** ``(number | `${number}x`)[] | undefined``
      - -

      - -Une liste de densités de pixels à générer pour l'image. - -#### `ImageTransform.quality` - -

      - -**Type :** ImageQuality | undefined -

      - -La qualité souhaitée pour l'image de sortie. - -#### `ImageTransform.format` - -

      - -**Type :** ImageOutputFormat | undefined -

      - -Le format souhaité pour l'image de sortie. - -#### `ImageTransform.fit` - -

      - -**Type :** `'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | string | undefined`
      - -

      - -Définit une liste de valeurs autorisées pour la propriété CSS `object-fit`, extensible avec n'importe quelle chaîne de caractères. - -#### `ImageTransform.position` - -

      - -**Type :** `string | undefined`
      - -

      - -Contrôle la valeur de la propriété CSS `object-position`. - ### `UnresolvedImageTransform`

      diff --git a/src/content/docs/ja/guides/backend/firebase.mdx b/src/content/docs/ja/guides/backend/firebase.mdx index 8e32b76c1f3bd..2b0e09bc97581 100644 --- a/src/content/docs/ja/guides/backend/firebase.mdx +++ b/src/content/docs/ja/guides/backend/firebase.mdx @@ -233,6 +233,7 @@ export const GET: APIRoute = async ({ request, cookies, redirect }) => { cookies.set("__session", sessionCookie, { path: "/", + maxAge: fiveDays / 1000, }); return redirect("/dashboard"); diff --git a/src/content/docs/ja/guides/integrations-guide/markdoc.mdx b/src/content/docs/ja/guides/integrations-guide/markdoc.mdx index 37d7055e1341b..854f36cd68165 100644 --- a/src/content/docs/ja/guides/integrations-guide/markdoc.mdx +++ b/src/content/docs/ja/guides/integrations-guide/markdoc.mdx @@ -113,13 +113,16 @@ Markdocファイルは、コンテンツコレクション内でのみ使用で - quick-start.mdoc -次に、[コンテンツコレクションAPI](/ja/guides/content-collections/#querying-build-time-collections)を使用してコレクションをクエリします。 +次に、[投稿やコレクションをクエリしてレンダリングします](/ja/guides/content-collections/#querying-build-time-collections) ```astro title="src/pages/why-markdoc.astro" --- -import { getEntry, render } from 'astro:content'; +import { getEntry, render } from "astro:content"; -const entry = await getEntry('docs', 'why-markdoc'); +const entry = await getEntry("docs", "why-markdoc"); +if (!entry) { + throw new Error("Entry not found"); +} const { Content } = await render(entry); --- @@ -142,6 +145,9 @@ const { Content } = await render(entry); import { getEntry, render } from 'astro:content'; const entry = await getEntry('docs', 'why-markdoc'); +if (!entry) { + throw new Error("Entry not found"); +} const { Content } = await render(entry); --- @@ -180,6 +186,9 @@ export default defineMarkdocConfig({ import { getEntry, render } from 'astro:content'; const entry = await getEntry('docs', 'why-markdoc'); +if (!entry) { + throw new Error("Entry not found"); +} const { Content } = await render(entry); --- @@ -500,8 +509,14 @@ Markdocの`image`タグを使用すると、`![]()`構文では不可能な追 const { src, alt, width, height, caption } = Astro.props; ---

      - - {caption &&
      {caption}
      } + { + typeof src === "string" ? ( + + ) : ( + + ) + } + {caption &&
      {caption}
      }
      ``` @@ -661,6 +676,37 @@ Markdocでネストされたタグを使用する場合、タグ内のコンテ {% /custom-tag %} ``` +### `typographer` + +

      + +**Type:** `boolean`
      +**Default:** `false`
      + +

      + +Markdocのスマートクォートの組み込みサポートを有効にします。通常のシングルクォートやダブルクォートを、適切なカーリークォートに置き換えられます。 + +```js title="astro.config.mjs" ins={6} + import { defineConfig } from 'astro/config'; + import markdoc from '@astrojs/markdoc'; + + export default defineConfig({ + // ... + integrations: [markdoc({ typographer: true })], + }); +``` + +```md title="src/content/docs/why-markdoc.mdoc" +She continued, "You know what they said? They said, 'It's astonishing how fancy these smart quotes look!'. That's what they said." +``` + +通常の引用符は自動的に変換され、以下のようにレンダリングされます。 + +```md +She continued, “You know what they said? They said, ‘It’s astonishing how fancy these smart quotes look!’. That’s what they said.” +``` + ## 例 * [Astro Markdocスターターテンプレート](https://github.com/withastro/astro/tree/latest/examples/with-markdoc)は、AstroプロジェクトでMarkdocファイルを使用する方法を示しています。 diff --git a/src/content/docs/ja/guides/migrate-to-astro/from-hugo.mdx b/src/content/docs/ja/guides/migrate-to-astro/from-hugo.mdx index f979564715f56..5f618fd1423a9 100644 --- a/src/content/docs/ja/guides/migrate-to-astro/from-hugo.mdx +++ b/src/content/docs/ja/guides/migrate-to-astro/from-hugo.mdx @@ -63,7 +63,7 @@ Markdownファイル内で変数や表現、UIコンポーネントなど動的 - + :::note[共有したいリソースがありますか?] diff --git a/src/content/docs/ja/guides/prefetch.mdx b/src/content/docs/ja/guides/prefetch.mdx new file mode 100644 index 0000000000000..c10832139db00 --- /dev/null +++ b/src/content/docs/ja/guides/prefetch.mdx @@ -0,0 +1,273 @@ +--- +title: プリフェッチ +description: ページ間のすばやいナビゲーションのためにリンクをプリフェッチします。 +i18nReady: true +--- + +import { Steps } from '@astrojs/starlight/components' +import Since from '~/components/Since.astro' + +ページのロード時間は、サイトの使いやすさとサイト全体の快適さに大きな影響を与えます。Astroの**オプトインのプリフェッチ**を利用すると、訪問者がマルチページアプリケーション(MPA)のサイトを操作する際に、ほぼ瞬時にページナビゲーションできるようになります。 + +## プリフェッチを有効にする + +プリフェッチは、`prefetch`設定で有効にできます。 + +```js title="astro.config.mjs" ins={4} +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + prefetch: true +}); +``` + +プリフェッチスクリプトがサイトのすべてのページに追加されます。その後、サイト上の任意の``リンクに`data-astro-prefetch`属性を追加することで、プリフェッチにオプトインできます。リンク上にホバーすると、スクリプトがページをバックグラウンドでフェッチします。 + +```html + +``` + +プリフェッチはサイト内のリンクに対してのみ機能し、外部リンクに対しては機能しないことに注意してください。 + +## プリフェッチの設定 + +`prefetch`設定は、プリフェッチをさらにカスタマイズするためのオプションのオブジェクトも受け付けます。 + +### プリフェッチ戦略 + +Astroは、さまざまなユースケースのために、以下の4種類のプリフェッチ戦略をサポートします。 + +- `hover`(デフォルト)。リンク上にホバーまたはフォーカスしたときにプリフェッチします。 +- `tap`。リンクをクリックする直前にプリフェッチします。 +- `viewport`。リンクがビューポートに入ったときにプリフェッチします。 +- `load`。ページのロード後に、ページ上のすべてのリンクをプリフェッチします。 + +個別のリンクに対して戦略を指定するには、戦略を`data-astro-prefetch`属性に渡します。 + +```html +About +``` + +各戦略は、必要なときにのみプリフェッチしてユーザーの帯域幅を節約するよう、細かく調整されています。たとえば、次のように動作します。 + +- 訪問者が[データ節約モード](https://developer.mozilla.org/ja/docs/Web/API/NetworkInformation/saveData)や[低速なコネクション](https://developer.mozilla.org/ja/docs/Web/API/NetworkInformation/effectiveType)を使っている場合、プリフェッチは`tap`戦略にフォールバックします。 +- リンク上にすばやくホバーやスクロールした場合はプリフェッチしません。 + +### デフォルトのプリフェッチ戦略 + +`data-astro-prefetch`属性を追加した場合のデフォルトのプリフェッチ戦略は`hover`です。デフォルトを変更するには、`astro.config.mjs`ファイルで[`prefetch.defaultStrategy`](/ja/reference/configuration-reference/#prefetchdefaultstrategy)を設定します。 + +```js title="astro.config.mjs" ins={4-6} +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + prefetch: { + defaultStrategy: 'viewport' + } +}); +``` + +### すべてのリンクをデフォルトでプリフェッチする + +`data-astro-prefetch`属性のないリンクも含めて、すべてのリンクをプリフェッチするには、[`prefetch.prefetchAll`](/ja/reference/configuration-reference/#prefetchprefetchall)を`true`に設定します。 + +```js title="astro.config.mjs" ins={4-6} +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + prefetch: { + prefetchAll: true + } +}); +``` + +その後、`data-astro-prefetch="false"`に設定することで、個別のリンクのプリフェッチをオプトアウトできます。 + +```html +About +``` + +すべてのリンクに対するデフォルトのプリフェッチ戦略は、[デフォルトのプリフェッチ戦略](#デフォルトのプリフェッチ戦略)に示したように、`prefetch.defaultStrategy`で変更できます。 + +## プログラムによるプリフェッチ + +ナビゲーションは常に``リンクとして表示されるとは限らないため、`astro:prefetch`モジュールの`prefetch()` APIを使用してプログラムからプリフェッチすることもできます。 + +```astro + + + +``` + +`prefetch()` APIには、同様の[データ節約モード](https://developer.mozilla.org/ja/docs/Web/API/NetworkInformation/saveData)と[低速なコネクション](https://developer.mozilla.org/ja/docs/Web/API/NetworkInformation/effectiveType)の検出機能があるため、必要なときにだけプリフェッチします。 + +低速なコネクションの検出を無視するには、`ignoreSlowConnection`オプションが利用できます。 + +```js +// データ節約モードや低速なコネクションの場合でもプリフェッチする +prefetch('/about', { ignoreSlowConnection: true }); +``` + +### `eagerness` + +

      +**型:** `'immediate' | 'eager' | 'moderate' | 'conservative'`
      +**デフォルト:** `'immediate'`
      + +

      + +実験的な[`clientPrerender`](/ja/reference/experimental-flags/client-prerender/)フラグを有効にすると、`prefetch()`の`eagerness`オプションを使用して、リンク先をどの程度積極的にプリフェッチまたはプリレンダリングするかをブラウザに提案できます。 + +このオプションは[Speculation Rules API](https://developer.mozilla.org/ja/docs/Web/HTML/Element/script/type/speculationrules#eagerness)で説明されているものと同じAPIに従い、デフォルトはもっとも積極的な`immediate`です。積極度の高い順に、ほかの選択肢は`eager`、`moderate`、`conservative`です。 + +`eagerness`オプションを使用すると、待ち時間を短縮するメリットと、サイト訪問者の帯域幅、メモリ、CPUのコストとのバランスを調整できます。Chromeなど一部のブラウザには、[過剰な投機的読み込み(リンクをプリレンダリングまたはプリフェッチしすぎること)を防ぐための制限](https://developer.chrome.com/blog/speculation-rules-improvements#chrome-limits)があります。 + +```astro +--- +--- + +``` + +多くのリンクをプログラムから`prefetch()`する場合は、`eagerness: 'moderate'`を設定できます。[先入れ先出し(FIFO)](https://ja.wikipedia.org/wiki/FIFO)方式とブラウザのヒューリスティックを活用し、どのリンクをどの順序でプリレンダリングまたはプリフェッチするかをブラウザに判断させられます。 + +```astro "{eagerness: 'moderate'}" +
      A Nice Link 1 +A Nice Link 2 +A Nice Link 3 +A Nice Link 4 +... +A Nice Link 20 + + +``` + +ブラウザのAPIに依存しているため、クライアント側のスクリプト内でのみ`prefetch()`をインポートするようにしてください。 + +## ビュートランジションとともに使用する + +ページで[Astroの``](/ja/guides/view-transitions/#enabling-view-transitions-spa-mode)を使用すると、プリフェッチもデフォルトで有効になります。`{ prefetchAll: true }`がデフォルトで設定され、ページ内の[すべてのリンクに対するプリフェッチ](#すべてのリンクをデフォルトでプリフェッチする)が有効になります。 + +デフォルトを上書きするには、`astro.config.mjs`内のプリフェッチ設定をカスタマイズできます。たとえば、次のように設定します。 + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + // プリフェッチを完全に無効化する + prefetch: false +}); +``` + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + // プリフェッチは維持するが、`data-astro-prefetch`があるリンクのみをプリフェッチする。 + prefetch: { + prefetchAll: false + } +}); +``` + +## ブラウザサポート + +Astroのプリフェッチは、ブラウザがサポートしている場合は[``](https://developer.mozilla.org/ja/docs/Web/HTML/Attributes/rel/prefetch)を使用し、サポートしていない場合は[`fetch()` API](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API)にフォールバックします。 + +主要なブラウザはAstroのプリフェッチをサポートしていますが、動作には若干の違いがあります。 + +### Chrome + +Chromeは``をサポートしており、プリフェッチは意図どおりに動作します。 + +また、[Speculation Rules API](https://developer.mozilla.org/ja/docs/Web/API/Speculation_Rules_API)の` ``` @@ -137,12 +142,12 @@ import Layout from 'src/layouts/Base.astro'; ```astro title="src/pages/index.astro" --- -import { auth } from "../../../lib/auth"; // Better Auth 인스턴스를 가져옵니다. +import { auth } from "../lib/auth"; // Better Auth 인스턴스를 가져옵니다. export const prerender = false; // 'server' 모드에서는 필요하지 않습니다. const session = await auth.api.getSession({ - headers: Astro.request.headers, + headers: Astro.request.headers, }); --- @@ -152,9 +157,9 @@ const session = await auth.api.getSession({ `auth` 객체를 사용하여 경로를 보호할 수도 있습니다. 다음 예시는 [Astro의 고급 라우팅](/ko/guides/routing/#고급-라우팅)과 [Hono](https://hono.dev/)를 사용하여 `/dashboard` 아래의 모든 경로에 인증된 세션을 요구하고, 그렇지 않으면 홈 페이지로 리디렉션합니다: ```ts title="src/fetch.ts" -import { Hono } from "hono"; +import { Hono, type Context, type Next } from "hono"; import { astro } from "astro/hono"; -import { auth } from "../auth"; // Better Auth 인스턴스를 가져옵니다. +import { auth } from "./lib/auth"; // Better Auth 인스턴스를 가져옵니다. const app = new Hono(); @@ -167,12 +172,14 @@ app.use(astro()); export default app; -async function requireAuth(c, next) { - const session = await auth.api.getSession({ headers: c.req.raw.headers }); - if (!session) { - return c.redirect("/"); - } - return next(); +async function requireAuth(c: Context, next: Next) { + const session = await auth.api.getSession({ + headers: c.req.raw.headers, + }); + if (!session) { + return c.redirect("/"); + } + return next(); } ``` @@ -225,37 +232,67 @@ Clerk는 사용자의 인증 상태에 따라 페이지의 표시 여부를 제 ```astro title="src/pages/index.astro" --- -import Layout from 'src/layouts/Base.astro'; -import { SignedIn, SignedOut, UserButton, SignInButton } from '@clerk/astro/components'; +import Layout from "../layouts/Base.astro"; +import { Show, UserButton, SignInButton } from "@clerk/astro/components"; export const prerender = false; // 'server' 모드에서는 필요하지 않습니다. --- + - - - - - - + + + + + + ``` -또한 Clerk를 사용하면 미들웨어를 통해 서버에서 경로를 보호할 수 있습니다. 보호할 경로를 지정하고 인증되지 않은 사용자에게 로그인하라는 메시지를 표시할 수 있습니다: +또한 Clerk를 사용하면 미들웨어를 통해 서버에서 경로를 보호할 수 있습니다: -```ts title="src/middleware.ts" -import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'; + -const isProtectedRoute = createRouteMatcher([ - '/dashboard(.*)', - '/forum(.*)', -]); +1. 미들웨어에서 `onRequest` 핸들러로 `clerkMiddleware()`를 설정하세요: -export const onRequest = clerkMiddleware((auth, context) => { - if (!auth().userId && isProtectedRoute(context.request)) { - return auth().redirectToSignIn(); - } -}); -``` + ```ts title="src/middleware.ts" + import { clerkMiddleware } from "@clerk/astro/server"; + + export const onRequest = clerkMiddleware({ + /* 옵션 */ + }); + ``` + +2. 페이지와 API 라우트에서 `locals.auth()`로 인증 상태에 접근하세요. 이를 통해 사용자가 인증되었는지 확인하고 적절한 조치(예: 로그인 페이지로 리디렉션하거나 다른 응답 반환)를 취할 수 있습니다. + + + + ```astro title="src/pages/dashboard.astro" + --- + const { isAuthenticated, redirectToSignIn } = Astro.locals.auth(); + + if (!isAuthenticated) return redirectToSignIn(); + --- + +

      Dashboard

      + ``` +
      + + ```ts title="src/pages/api/data.ts" + import type { APIRoute } from "astro"; + + export const GET: APIRoute = ({ locals }) => { + const { isAuthenticated, userId } = locals.auth(); + + if (!isAuthenticated) { + return new Response("Unauthorized", { status: 401 }); + } + + return Response.json({ userId }); + }; + ``` + +
      +
      ### 다음 단계 @@ -280,12 +317,14 @@ export const onRequest = clerkMiddleware((auth, context) => { - [Astro에서 GitHub OAuth를 구현하는 예시](https://github.com/lucia-auth/example-astro-github-oauth) - [Astro에서 Google OAuth를 구현하는 예시](https://github.com/lucia-auth/example-astro-google-oauth) -- [Astro에서 2FA를 사용하여 이메일 및 비밀번호를 구현하는 예시 example](https://github.com/lucia-auth/example-astro-email-password-2fa) +- [Astro에서 2FA를 사용하여 이메일 및 비밀번호를 구현하는 예시](https://github.com/lucia-auth/example-astro-email-password-2fa) - [Astro에서 2FA와 WebAuthn를 사용하여 이메일 및 비밀번호를 구현하는 예시](https://github.com/lucia-auth/example-astro-email-password-webauthn) ## Scalekit -[Scalekit](https://scalekit.com/)은 B2B 및 AI 애플리케이션을 위해 구축된 인증 플랫폼입니다. 소셜 로그인, 엔터프라이즈 SSO, 매직 링크 등을 제공합니다 — 전체 OAuth 2.0 / OIDC 흐름을 관리하므로 로그인 UI를 구축할 필요 없이 토큰과 사용자 프로필을 얻을 수 있습니다. 단일 Scalekit 환경이 여러 애플리케이션을 지원하므로 사용자가 한 번 인증하면 모든 속성(예: `app.yourcompany.com` 및 `docs.yourcompany.com`)에서 동일한 세션을 공유합니다. +[Scalekit](https://scalekit.com/)은 B2B 및 AI 애플리케이션을 위한 인증 플랫폼입니다. 전체 OAuth 2.0 및 OIDC 흐름을 관리하며, 소셜 로그인, 엔터프라이즈 SSO, 매직 링크 등의 방식을 지원합니다. 그런 다음 별도의 로그인 UI 없이도 토큰과 사용자 프로필을 반환합니다. + +단일 Scalekit 환경은 여러 애플리케이션을 지원할 수 있습니다. 이를 통해 한 번 인증하면 모든 속성(예: `app.yourcompany.com` 및 `docs.yourcompany.com`)에서 동일한 세션을 공유할 수 있습니다. ### 가이드 diff --git a/src/content/docs/ko/guides/backend/firebase.mdx b/src/content/docs/ko/guides/backend/firebase.mdx index ab6a85ecbcc5b..b148f4c357d5c 100644 --- a/src/content/docs/ko/guides/backend/firebase.mdx +++ b/src/content/docs/ko/guides/backend/firebase.mdx @@ -233,6 +233,7 @@ export const GET: APIRoute = async ({ request, cookies, redirect }) => { cookies.set("__session", sessionCookie, { path: "/", + maxAge: fiveDays / 1000, }); return redirect("/dashboard"); diff --git a/src/content/docs/ko/guides/cms/emdash.mdx b/src/content/docs/ko/guides/cms/emdash.mdx index 3a593ca2bdf96..39fbe39d0a377 100644 --- a/src/content/docs/ko/guides/cms/emdash.mdx +++ b/src/content/docs/ko/guides/cms/emdash.mdx @@ -4,13 +4,234 @@ description: EmDash를 CMS로 사용하여 Astro 프로젝트에 콘텐츠를 sidebar: label: EmDash type: cms -stub: true logo: emdash i18nReady: true --- +import { Steps } from '@astrojs/starlight/components'; +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; [EmDash](https://emdashcms.com/)는 Astro를 위해 특별히 구축된 오픈 소스 풀스택 CMS로, 사이트에 데이터베이스 기반 콘텐츠, 관리자 UI, 미디어 라이브러리, 메뉴 및 분류 체계를 추가합니다. +:::tip +**새 Astro + EmDash 프로젝트를 처음부터 시작하려면**, EmDash CLI를 사용하여 미리 구성된 프로젝트를 생성하세요: + + + + ```shell + npm create emdash@latest + ``` + + + ```shell + pnpm create emdash@latest + ``` + + + ```shell + yarn create emdash@latest + ``` + + +::: + +## Astro와 통합 + +EmDash는 Astro 프로젝트 내에서 실행되며 데이터베이스를 사용합니다. `/_emdash/admin`에서 관리 인터페이스에 접근하여 콘텐츠를 편집할 수 있습니다. 페이지는 [라이브 콘텐츠 컬렉션](/ko/guides/content-collections/#라이브-콘텐츠-컬렉션)을 사용하여 데이터베이스를 쿼리함으로써 콘텐츠를 표시합니다. + +이 가이드에서는 [Node.js 어댑터](/ko/guides/integrations-guide/node/)와 로컬 SQLite 데이터베이스를 사용합니다. 지원되는 데이터베이스 목록은 [EmDash 문서](https://docs.emdashcms.com/deployment/database/)를 참조하세요. + +## 전제 조건 + +- [어댑터](/ko/guides/on-demand-rendering/)와 함께 구성되고 [`output: "server"`](/ko/reference/configuration-reference/#output)로 설정된 기존 Astro 프로젝트 (Astro 6 이상). +- Node.js v22.16.0 이상. + +## 종속성 설치 + +React는 EmDash 관리 인터페이스를 구동하며 필수 종속성입니다. 프로젝트에서 React를 사용하지 않는다면, 패키지 관리자에 맞는 `astro add` 명령을 사용하여 설치하세요: + + + + ```shell + npx astro add react + ``` + + + ```shell + pnpm astro add react + ``` + + + ```shell + yarn astro add react + ``` + + + +EmDash 패키지도 설치해야 합니다: + + + + ```shell + npm install emdash + ``` + + + ```shell + pnpm add emdash + ``` + + + ```shell + yarn add emdash + ``` + + + +## 통합 추가하기 + +Astro 구성 파일에 `emdash()` 통합을 추가하고, 데이터베이스와 미디어 스토리지 백엔드를 구성하세요: + +```js title="astro.config.mjs" ins={4-5, 12-18} +import { defineConfig } from "astro/config"; +import node from "@astrojs/node"; +import react from "@astrojs/react"; +import emdash, { local } from "emdash/astro"; +import { sqlite } from "emdash/db"; + +export default defineConfig({ + output: "server", + adapter: node({ mode: "standalone" }), + integrations: [ + react(), + emdash({ + database: sqlite({ url: "file:./data.db" }), + storage: local({ + directory: "./uploads", + baseUrl: "/_emdash/api/media/file", + }), + }), + ], +}); +``` + +## 라이브 컬렉션 로더 추가하기 + +Astro의 콘텐츠 계층이 EmDash 콘텐츠를 해석할 수 있도록 `src/live.config.ts` 파일을 생성하세요: + +```ts title="src/live.config.ts" +import { defineLiveCollection } from "astro:content"; +import { emdashLoader } from "emdash/runtime"; + +export const collections = { + _emdash: defineLiveCollection({ loader: emdashLoader() }), +}; +``` + +`_emdash` 컬렉션은 내부적으로 콘텐츠 타입(예: posts와 pages)으로 라우팅됩니다. `src/content.config.ts`에 있는 기존 파일 기반 컬렉션은 그대로 함께 계속 동작합니다. + +## EmDash 로컬에서 실행하기 + +자체 페이지를 테스트하기 전에 설정 마법사를 완료해야 합니다. 설정이 완료될 때까지 게시된 콘텐츠가 없으며 쿼리는 빈 결과를 반환합니다. + + +1. 데이터베이스를 초기화하고 관리 UI를 실행하려면 Astro 개발 서버를 시작하세요: + + + + ```shell + npm run dev + ``` + + + ```shell + pnpm run dev + ``` + + + ```shell + yarn run dev + ``` + + + + 처음 실행하면 EmDash는 스키마와 두 개의 기본 컬렉션인 `pages`와 `posts`를 포함하는 `data.db`를 생성합니다. + +2. 브라우저에서 `http://localhost:4321/_emdash/admin`을 방문하세요. EmDash가 설정 마법사로 리디렉션합니다. + +3. **Site** 단계에서 사이트 제목과 선택 사항인 태그라인을 입력하세요. + +4. **Account** 단계에서 이메일 주소와 이름을 입력하세요. 이렇게 하면 관리자 계정이 생성됩니다. + +5. **Sign In** 단계에서 계정을 보호하세요. 기기의 생체 인식 인증, 보안 키 또는 PIN으로 패스키를 등록하려면 **Create Passkey**를 선택하세요. + +6. 새 패스키로 로그인하여 대시보드로 이동하세요. + + +## 첫 게시물 만들기 + + +1. 대시보드에서 **+ Post** 버튼을 클릭하세요. + +2. 제목과 콘텐츠를 추가하세요. EmDash는 리치 텍스트를 블록 편집기에서 편집되는 [Portable Text](https://github.com/portabletext/portabletext)로 저장합니다. URL 슬러그는 제목에서 생성되며 사이드바에서 편집할 수 있습니다. + +3. **Save**를 클릭한 다음 **Publish**를 클릭하세요. 게시된 게시물만 사이트 방문자에게 표시됩니다. + + +## EmDash 콘텐츠 렌더링하기 + +`getEmDashCollection()`과 `getEmDashEntry()`로 콘텐츠를 쿼리하세요. 둘 다 라이브 컬렉션 패턴을 따르며 요청 시점에 결과를 반환하므로, 다시 빌드하지 않아도 게시된 변경 사항이 표시됩니다. + +### 게시물 목록 표시하기 + +다음 예시는 게시된 모든 게시물 제목 목록을 표시하며, 각 제목은 개별 게시물 페이지로 연결됩니다: + +```astro title="src/pages/blog.astro" +--- +import { getEmDashCollection } from "emdash"; + +const { entries: posts } = await getEmDashCollection("posts", { + status: "published", +}); +--- + +``` + +### 단일 게시물 표시하기 + +개별 게시물의 콘텐츠를 표시하려면 슬러그로 게시물을 가져온 뒤 `` 컴포넌트로 Portable Text 콘텐츠를 렌더링하세요: + +```astro title="src/pages/posts/[...slug].astro" +--- +import { getEmDashEntry } from "emdash"; +import { PortableText } from "emdash/ui"; + +const { slug } = Astro.params; +const { entry: post } = await getEmDashEntry("posts", slug); + +if (!post) { + return Astro.redirect("/404"); +} +--- +
      +

      {post.data.title}

      + +
      +``` + +필터링, 페이지네이션, 초안 미리보기, 비주얼 편집에 대한 자세한 내용은 [EmDash 쿼리 가이드](https://docs.emdashcms.com/guides/querying-content/)를 참조하세요. + +## EmDash + Astro 배포하기 + +EmDash는 단일 Astro 프로젝트로서 사이트와 함께 배포됩니다. 어댑터를 지원하는 호스트를 선택하고, 프로덕션 데이터베이스와 미디어 스토리지를 프로비저닝하세요. + +구체적인 지침은 [Node.js용 EmDash 배포 가이드](https://docs.emdashcms.com/deployment/nodejs/)와 [Cloudflare용 EmDash 배포 가이드](https://docs.emdashcms.com/deployment/cloudflare/)를 참조하세요. 선호하는 호스팅 제공업체로 배포하려면 Astro의 [배포 가이드](/ko/guides/deploy/)를 방문하여 지침을 따를 수도 있습니다. + ## 공식 리소스 - [Astro 개발자를 위한 EmDash 문서](https://docs.emdashcms.com/coming-from/astro/) diff --git a/src/content/docs/ko/guides/deploy/ishosting.mdx b/src/content/docs/ko/guides/deploy/ishosting.mdx new file mode 100644 index 0000000000000..f8ed741f32b09 --- /dev/null +++ b/src/content/docs/ko/guides/deploy/ishosting.mdx @@ -0,0 +1,16 @@ +--- +title: Astro 사이트를 is*hosting에 배포 +description: is*hosting을 사용하여 Astro 사이트를 웹에 배포하는 방법. +sidebar: + label: is*hosting +type: deploy +logo: ishosting +supports: ['ssr', 'static'] +i18nReady: true +--- + +[is\*hosting](https://ishosting.com/)은 40개 이상의 지역에서 VPS 및 전용 서버를 제공하는 호스팅 제공업체로, 정적 또는 서버 렌더링(SSR) Astro 사이트를 셀프 호스팅하는 데 사용할 수 있습니다. + +## 공식 리소스 + +- [is\*hosting 가이드: VPS에 Astro 배포하기(정적 및 SSR)](https://blog.ishosting.com/en/astro-on-vps) diff --git a/src/content/docs/ko/guides/integrations-guide/cloudflare.mdx b/src/content/docs/ko/guides/integrations-guide/cloudflare.mdx index 437e3860d3dcc..57840194f3d44 100644 --- a/src/content/docs/ko/guides/integrations-guide/cloudflare.mdx +++ b/src/content/docs/ko/guides/integrations-guide/cloudflare.mdx @@ -389,12 +389,12 @@ Cloudflare `workerd` 런타임은 일부 [비표준 모듈 유형](https://devel 다음은 요청의 숫자 매개변수를 함께 더하여 요청에 응답하는 Wasm 모듈을 가져오는 예시입니다. -```js title="pages/add/[a]/[b].js" +```js title="src/pages/add/[a]/[b].js" // WebAssembly 모듈 가져오기 import mod from '../util/add.wasm'; // 사용하려면 먼저 인스턴스화해야 합니다. -const addModule: any = new WebAssembly.Instance(mod); +const addModule = new WebAssembly.Instance(mod); export async function GET(context) { const a = Number.parseInt(context.params.a); @@ -463,16 +463,20 @@ Cloudflare 어댑터는 [고급 라우팅 파이프라인](/ko/guides/routing/# [`astro/fetch`](/ko/reference/modules/astro-fetch/)와 함께 사용하기 위한 API입니다. `@astrojs/cloudflare/fetch`에서 가져온 `cf()` 함수는 [`FetchState`](/ko/reference/modules/astro-fetch/#fetchstate), Cloudflare `env`, 그리고 `ExecutionContext`를 인자로 받습니다. 이 함수는 정적 자산과 일치할 때는 `Response`를 반환하고, 요청이 Astro 렌더링으로 계속 진행되어야 할 때는 `undefined`를 반환합니다. +

      + +같은 `FetchState`와 Astro 파이프라인에서 받은 응답을 반환하기 전에 `finalize()`에 전달하세요. 이렇게 하면 렌더링 중 생성된 쿠키와 어댑터의 기본 Cloudflare CDN 캐시 헤더가 응답에 적용됩니다. + ```ts title="src/worker.ts" import { astro, FetchState } from 'astro/fetch'; -import { cf } from '@astrojs/cloudflare/fetch'; +import { cf, finalize } from '@astrojs/cloudflare/fetch'; export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { const state = new FetchState(request); const asset = await cf(state, env, ctx); if (asset) return asset; - return astro(state); + return finalize(state, await astro(state)); }, }; ``` @@ -483,6 +487,8 @@ export default { [`astro/hono`](/ko/reference/modules/astro-hono/)와 함께 사용하기 위한 API입니다. `@astrojs/cloudflare/hono`에서 가져온 `cf()` 함수는 Hono 컨텍스트에서 `env`와 `executionCtx`를 자동으로 읽어오는 Hono 미들웨어를 반환합니다. +`@astrojs/cloudflare` v14.3.0 이상에서는 이 미들웨어가 다운스트림 Hono 핸들러가 실행된 뒤 응답을 finalize합니다. 렌더링 중 생성된 쿠키와 어댑터의 기본 Cloudflare CDN 캐시 헤더가 자동으로 적용됩니다. + ```ts title="src/worker.ts" import { Hono } from 'hono'; import { actions, middleware, pages, i18n } from 'astro/hono'; diff --git a/src/content/docs/ko/guides/integrations-guide/markdoc.mdx b/src/content/docs/ko/guides/integrations-guide/markdoc.mdx index e7b5eb6fdb3af..f1a8e2bf5a974 100644 --- a/src/content/docs/ko/guides/integrations-guide/markdoc.mdx +++ b/src/content/docs/ko/guides/integrations-guide/markdoc.mdx @@ -117,9 +117,12 @@ Markdoc 파일은 콘텐츠 컬렉션에서만 사용할 수 있습니다. `.mdo ```astro title="src/pages/why-markdoc.astro" --- -import { getEntry, render } from 'astro:content'; +import { getEntry, render } from "astro:content"; -const entry = await getEntry('docs', 'why-markdoc'); +const entry = await getEntry("docs", "why-markdoc"); +if (!entry) { + throw new Error("항목을 찾을 수 없습니다."); +} const { Content } = await render(entry); --- @@ -142,6 +145,9 @@ const { Content } = await render(entry); import { getEntry, render } from 'astro:content'; const entry = await getEntry('docs', 'why-markdoc'); +if (!entry) { + throw new Error("항목을 찾을 수 없습니다."); +} const { Content } = await render(entry); --- @@ -180,6 +186,9 @@ entry 객체의 `data` 속성을 콘텐츠를 렌더링하기 위한 변수로 import { getEntry, render } from 'astro:content'; const entry = await getEntry('docs', 'why-markdoc'); +if (!entry) { + throw new Error("항목을 찾을 수 없습니다."); +} const { Content } = await render(entry); --- @@ -495,9 +504,16 @@ Markdoc `image` 태그를 사용하면 `![]()` 구문으로는 불가능한 이 } const { src, alt, width, height, caption } = Astro.props; --- +
      - - {caption &&
      {caption}
      } + { + typeof src === "string" ? ( + + ) : ( + + ) + } + {caption &&
      {caption}
      }
      ``` diff --git a/src/content/docs/ko/guides/integrations-guide/mdx.mdx b/src/content/docs/ko/guides/integrations-guide/mdx.mdx index af5625dd5ff06..24f4af6494607 100644 --- a/src/content/docs/ko/guides/integrations-guide/mdx.mdx +++ b/src/content/docs/ko/guides/integrations-guide/mdx.mdx @@ -133,11 +133,11 @@ export const title = 'My first MDX post' ```astro title="src/pages/index.astro" --- -const matches = import.meta.glob('./posts/*.mdx', { eager: true }); +const matches = import.meta.glob("./posts/*.mdx", { eager: true }); const posts = Object.values(matches); --- -{posts.map(post =>

      {post.title}

      )} +{posts.map((post: any) =>

      {post.title}

      )} ``` #### 내보낸 속성 @@ -240,11 +240,15 @@ MDX 파일이 콘텐츠 컬렉션 항목인 경우, `astro:content`의 `render() ```astro title="src/pages/blog/post-1.astro" ins="components={{ h1: CustomHeading }}" --- -import { getEntry, render } from 'astro:content'; -import CustomHeading from '../../components/CustomHeading.astro'; -const entry = await getEntry('blog', 'post-1'); +import { getEntry, render } from "astro:content"; +import CustomHeading from "../../components/CustomHeading.astro"; +const entry = await getEntry("blog", "post-1"); +if (!entry) { + throw new Error("항목을 찾을 수 없습니다."); +} const { Content } = await render(entry); --- + ``` @@ -358,20 +362,22 @@ MDX는 기본적으로 [프로젝트의 기존 Markdown 구성](/ko/reference/co ```js title="astro.config.mjs" import { defineConfig } from 'astro/config'; -import { satteri } from '@astrojs/markdown-satteri'; -import mdx from '@astrojs/mdx'; +import { satteri } from "@astrojs/markdown-satteri"; +import mdx from "@astrojs/mdx"; +import mdastPlugin1 from "./src/mdast-plugin-1"; +import mdastPlugin2 from "./src/mdast-plugin-2"; export default defineConfig({ // ... markdown: { - syntaxHighlight: 'prism', + syntaxHighlight: "prism", processor: satteri({ mdastPlugins: [mdastPlugin1] }), }, integrations: [ mdx({ // Markdown `syntaxHighlight` 재정의 // `.mdx` 파일은 대신 Shiki 사용 - syntaxHighlight: 'shiki', + syntaxHighlight: "shiki", // 이 옵션은 `.mdx` 파일의 `markdown.processor`를 재정의함 processor: satteri({ mdastPlugins: [mdastPlugin2] }), @@ -384,8 +390,9 @@ MDX에서 `markdown` 구성 확장을 비활성화해야 할 수도 있습니다 ```js title="astro.config.mjs" import { defineConfig } from 'astro/config'; -import { satteri } from '@astrojs/markdown-satteri'; -import mdx from '@astrojs/mdx'; +import { satteri } from "@astrojs/markdown-satteri"; +import mdx from "@astrojs/mdx"; +import mdastPlugin from "./src/mdast-plugin"; export default defineConfig({ // ... @@ -466,10 +473,10 @@ MDX 최적화 프로그램이 [컴포넌트 prop을 통해 가져온 MDX 콘텐 예를 들어 다음 의도된 MDX 출력은 모두 `"

      ...

      "`이 아닌 `...`입니다. -```astro +```astro title="src/pages/any-page.astro" --- -import { Content, components } from '../content.mdx'; -import Heading from '../Heading.astro'; +import { Content, components } from "../content.mdx"; +import Heading from "../components/Heading.astro"; --- diff --git a/src/content/docs/ko/guides/integrations-guide/partytown.mdx b/src/content/docs/ko/guides/integrations-guide/partytown.mdx index f7dcab16966c0..955563d973227 100644 --- a/src/content/docs/ko/guides/integrations-guide/partytown.mdx +++ b/src/content/docs/ko/guides/integrations-guide/partytown.mdx @@ -153,7 +153,10 @@ export default defineConfig({ 일부 타사 스크립트는 서비스 워커에서 실행되는 `config.resolveUrl()`을 통한 [프록시](https://partytown.qwik.dev/proxying-requests/)가 필요할 수 있습니다. 이 구성 옵션을 설정하여 특정 URL을 확인하고, 대신 프록시된 URL을 선택적으로 반환할 수 있습니다. -```js title="astro.config.mjs" {7-13} +```js title="astro.config.mjs" {10-15} +import { defineConfig } from 'astro/config'; +import partytown from "@astrojs/partytown"; + export default defineConfig({ // ... integrations: [ @@ -161,13 +164,12 @@ export default defineConfig({ // 예시: Facebook의 분석 스크립트 프록시 config: { resolveUrl: (url) => { - const proxyMap = { - "connect.facebook.net": "my-proxy.com" - } - url.hostname = proxyMap[url.hostname] || url.hostname; + const proxyMap = new Map([["connect.facebook.net", "my-proxy.com"]]); + const proxiedHost = proxyMap.get(url.hostname); + if (proxiedHost) url.hostname = proxiedHost; return url; }, - } + }, }), ], }); diff --git a/src/content/docs/ko/guides/media/cloudinary.mdx b/src/content/docs/ko/guides/media/cloudinary.mdx index 7a54a2aa5fda6..d2e452caea3ef 100644 --- a/src/content/docs/ko/guides/media/cloudinary.mdx +++ b/src/content/docs/ko/guides/media/cloudinary.mdx @@ -121,7 +121,7 @@ Cloudinary Astro SDK는 콘텐츠 컬렉션을 위한 Cloudinary 자산을 불 이미지 또는 동영상 컬렉션을 불러오려면 `loader: cldAssetsLoader ({})`의 `folder`를 설정합니다 (필요한 경우): -```jsx title="config.ts" +```jsx title="content.config.ts" import { defineCollection } from 'astro:content'; import { cldAssetsLoader } from 'astro-cloudinary/loaders'; diff --git a/src/content/docs/ko/guides/media/imagekit.mdx b/src/content/docs/ko/guides/media/imagekit.mdx index c22ed310bb97d..6ff10090dc74a 100644 --- a/src/content/docs/ko/guides/media/imagekit.mdx +++ b/src/content/docs/ko/guides/media/imagekit.mdx @@ -428,7 +428,7 @@ const gallery = defineCollection({ })); }, schema: z.object({ - url: z.string().url(), + url: z.url(), width: z.number(), height: z.number(), name: z.string(), diff --git a/src/content/docs/ko/guides/media/mux.mdx b/src/content/docs/ko/guides/media/mux.mdx index cd1dac4f70904..93c1c67324521 100644 --- a/src/content/docs/ko/guides/media/mux.mdx +++ b/src/content/docs/ko/guides/media/mux.mdx @@ -87,6 +87,10 @@ Astro 프로젝트에서 Mux Player를 다음과 같이 사용할 수도 있습 [Mux 웹 플레이어를 제어하는 다른 모든 옵션](https://www.mux.com/docs/guides/player-api-reference/?utm_campaign=21819274-Astro&utm_source=astro-docs) (예: 컨트롤 표시/숨기기, 스타일 요소, 쿠키 비활성화)은 선택 사항입니다. ```astro title="src/components/StarlightVideo.astro" +--- +import { MuxPlayer } from "@mux/mux-player-astro"; +--- + - ``` 모든 라이브 스트림은 향후 요청 시 재생할 수 있도록 Mux에 동영상 자산으로 녹화 및 저장됩니다. @@ -212,7 +219,7 @@ const mux = new Mux ({ Astro 프로젝트에서 사용할 동영상 정보를 가져오려면 동영상의 `ASSET_ID` (Mux 대시보드에서 확인 가능)를 `retrieve()` 도우미 함수에 제공하세요. 이를 통해 동영상 제목이나 재생 시간과 같은 값을 Mux 컴포넌트와 HTML 템플릿에 전달할 수 있습니다. -```astro +```astro title="src/components/StarlightVideo.astro" --- import Mux from "@mux/mux-node"; import { MuxPlayer } from "@mux/mux-player-astro"; @@ -278,9 +285,9 @@ Mux Uploader는 파일 업로드 시 수동 파일 선택과 드래그 앤 드 동영상을 업로드하기 전에 [Mux API 액세스 토큰](#mux-환경-api-액세스)이 구성되어 있는지 확인하세요. 토큰이 구성되면 Mux Node SDK의 `create()` 함수를 사용하여 새 동영상 업로드를 시작할 수 있습니다. -```astro +```astro title="src/components/VideoUploader.astro" --- -import Layout from '../../layouts/Layout.astro'; +import Layout from '../layouts/Layout.astro'; import Mux from "@mux/mux-node"; import { MuxUploader } from "@mux/mux-uploader-astro"; @@ -306,7 +313,7 @@ const upload = await mux.video.uploads.create({ 추가 컴포넌트 속성을 사용하여 ``의 기능과 모양을 사용자 정의할 수 있습니다. 요소 스타일링 외에도, 다운로드 일시 중지 기능이나 최대 파일 크기 설정과 같은 옵션을 제어할 수 있습니다. -```astro +```astro title="src/components/VideoUploader.astro" --- import { MuxUploader } from '@mux/mux-uploader-astro'; --- @@ -331,7 +338,7 @@ Mux Uploader는 풍부한 기능과 미디어 업로드의 현재 상태에 따 이러한 이벤트를 수신 대기하고 Astro 컴포넌트의 [클라이언트 측 스크립트](/ko/guides/client-side-scripts/)에서 처리할 수 있습니다. `MuxUploaderElement` 타입도 사용할 수 있습니다. -```astro +```astro title="src/components/VideoUploader.astro" --- import { MuxUploader } from '@mux/mux-uploader-astro'; --- diff --git a/src/content/docs/ko/reference/cache-provider-reference.mdx b/src/content/docs/ko/reference/cache-provider-reference.mdx index e50fdcb91c262..5a05069b72628 100644 --- a/src/content/docs/ko/reference/cache-provider-reference.mdx +++ b/src/content/docs/ko/reference/cache-provider-reference.mdx @@ -371,10 +371,46 @@ stale-while-revalidate 유효 기간을 초 단위로 지정합니다. 백그라

      -**타입:** (context: \{ request: Request; url: URL; waitUntil?: (promise: Promise\) => void \}, next: MiddlewareNext) => Promise\ +**타입:** (context: \{ request: Request; url: URL; waitUntil?: (promise: Promise\) => void; logger: AstroRuntimeLogger \}, next: MiddlewareNext) => Promise\

      -런타임 캐싱을 구현하기 위해 요청을 가로챕니다. `context`에는 배포 환경의 런타임이 기능을 지원하는 경우, stale-while-revalidate와 같은 백그라운드 작업을 처리할 수 있는 `waitUntil()` 함수가 포함되어 있습니다. +Astro가 일치하는 라우트를 생성하기 전에 요청을 가로채는 선택적 훅입니다. 첫 번째 인자로 `context` 객체를 받고, 체인의 `next()` 미들웨어를 호출하기 위한 콜백을 받습니다. + +`context`에는 다음 속성이 포함됩니다: + +- `request`: 들어오는 [`Request`](https://developer.mozilla.org/ko/docs/Web/API/Request) 객체입니다. +- `url`: 요청에서 파생된 정규화된 [`URL`](https://developer.mozilla.org/ko/docs/Web/API/URL)입니다. +- `waitUntil()`: 런타임에서 지원되는 경우, 오래된 캐시 항목 재검증과 같은 백그라운드 작업을 정의하는 함수입니다. +- `logger`: Astro v7.3.0부터 사용할 수 있는 [`logger`](/ko/reference/api-reference/#logger) 인스턴스로, [구성된 로깅 대상](/ko/reference/configuration-reference/#로거-옵션)을 따릅니다. + +다음 예시는 캐시에 추가된 각 URL에 대한 로그를 남기는 최소한의 `onRequest()` 훅을 구현합니다. + +```ts title="my-provider/runtime.ts" ins={7-17} +import type { CacheProviderFactory } from 'astro'; + +const factory: CacheProviderFactory = (config) => { + const cache = new Map(); + return { + name: 'my-cache-provider', + async onRequest({ request, url, waitUntil, logger }, next) { + if (request.method !== 'GET') return next(); + + const cached = cache.get(url); + if (cached) return cached; + + const response = await next(); + cache.set(url, response.clone()); + logger.info(`Cached response for ${url}.`); + return response; + }, + async invalidate() { + // ... + }, + }; +}; + +export default factory; +``` #### `CacheProvider.invalidate()` diff --git a/src/content/docs/ko/reference/cli-reference.mdx b/src/content/docs/ko/reference/cli-reference.mdx index d1191dbfbfd1a..582de74aad0dc 100644 --- a/src/content/docs/ko/reference/cli-reference.mdx +++ b/src/content/docs/ko/reference/cli-reference.mdx @@ -194,25 +194,7 @@ Astro 개발 서버가 실행 중인 터미널에서 다음 단축키를 사용 - `o + enter` 브라우저에서 Astro 사이트를 엽니다. - `q + enter` 개발 서버를 종료합니다. -

      플래그

      - -

      - -이 명령어는 [공통 플래그](#공통-플래그)와 다음과 같은 추가 플래그를 허용합니다. - -#### `--ignore-lock` - -

      - -다른 실행 중인 개발 서버를 감지하는 데 사용되는 잠금 파일을 확인하거나 기록하지 않고 개발 서버를 시작합니다. 이를 통해 오류를 발생시키는 대신, 동일한 프로젝트에서 이미 실행 중인 개발 서버와 함께 새 개발 서버를 시작할 수 있습니다. - -```shell -astro dev --ignore-lock --port 4322 -``` - -새로 시작한 서버는 [`stop`, `status`, `logs` 하위 명령어](#공통-하위-명령어)로 추적되지 않습니다. - -`--background`(AI 코딩 에이전트가 실행한 경우 포함) 또는 `--force`와 함께 사용하면 오류가 발생합니다. 두 옵션 모두 잠금 파일에 의존하기 때문입니다. +개발 환경을 더욱 세부적으로 제어하기 위해 이 명령어는 [공통 플래그](#공통-플래그) 및 [공통 하위 명령어](#공통-하위-명령어)와 함께 사용할 수 있습니다. ## `astro build` @@ -238,7 +220,7 @@ Astro 미리보기 서버가 실행 중인 터미널에서 다음 단축키를 - `o` + `enter`: 브라우저에서 Astro 사이트를 엽니다. - `q` + `enter`: 미리보기 서버를 종료합니다. -`astro preview` 명령은 아래에 설명된 [공통 플래그](#공통-플래그)와 결합하여 미리보기 환경을 더욱 세부적으로 제어할 수 있습니다. Astro v7.2.0부터는 [`--background` 플래그](#--background)와 [`stop`, `status`, `logs` 하위 명령어](#공통-하위-명령어)도 지원하여 백그라운드 미리보기 서버를 관리할 수 있습니다. +이 명령어는 미리보기 환경을 더욱 세부적으로 제어하기 위해 [공통 플래그](#공통-플래그) 및 [공통 하위 명령어](#공통-하위-명령어)와 함께 사용할 수 있습니다. ## `astro check` @@ -558,6 +540,20 @@ astro dev --background --force 기계가 읽을 수 있는 형식의 출력에 유용한 [JSON 로깅](/ko/reference/logger-reference/#loghandlersjson)을 활성화합니다. +### `--ignore-lock` + +

      + +잠금 파일의 존재 여부를 확인하거나 해당 파일을 작성할 필요가 없도록 합니다. v7.3.0부터, 이를 통해 새로운 개발 서버나 프리뷰 서버가 이미 실행 중인 서버와 함께 시작될 수 있으며, 오류가 발생하지 않습니다. + +```shell +astro dev --ignore-lock --port 4322 +``` + +새 서버는 [공통 하위 명령어](#공통-하위-명령어)로 추적되지 않습니다. + +[`--background`](#--background) 또는 [`--force`](#--force-string)와 함께 사용하면 오류가 발생합니다. 두 옵션 모두 잠금 파일에 의존하기 때문입니다. + ## 전역 플래그 이 플래그를 사용하여 `astro` CLI에 대한 정보를 얻으세요. diff --git a/src/content/docs/ko/reference/errors/redirect-with-no-location.mdx b/src/content/docs/ko/reference/errors/redirect-with-no-location.mdx index 64f7540cb861d..2b97a9c79e43f 100644 --- a/src/content/docs/ko/reference/errors/redirect-with-no-location.mdx +++ b/src/content/docs/ko/reference/errors/redirect-with-no-location.mdx @@ -4,6 +4,8 @@ i18nReady: true githubURL: https://github.com/withastro/astro/blob/main/packages/astro/src/core/errors/errors-data.ts --- +> **RedirectWithNoLocation**: The redirect `Response` has no `Location` header. Use `Astro.redirect()` to create redirects, or add a `Location` header to the `Response`. + ## 무엇이 잘못되었나요? 리디렉션에는 `Location` 헤더가 있는 위치가 제공되어야 합니다. diff --git a/src/content/docs/ko/reference/image-service-reference.mdx b/src/content/docs/ko/reference/image-service-reference.mdx index 0821f348cc2e4..a056a323ba9ab 100644 --- a/src/content/docs/ko/reference/image-service-reference.mdx +++ b/src/content/docs/ko/reference/image-service-reference.mdx @@ -32,21 +32,21 @@ Astro는 로컬과 외부라는 두 가지 유형의 이미지 서비스를 제 import type { ExternalImageService, ImageTransform, AstroConfig } from "astro"; const service: ExternalImageService = { - validateOptions(options: ImageTransform, imageConfig: AstroConfig['image']) { + validateOptions(options: ImageTransform, imageConfig: AstroConfig['image'], logger) { const serviceConfig = imageConfig.service.config; // 사용자가 설정한 최대 너비를 적용합니다. if (options.width && options.width > serviceConfig.maxWidth) { - console.warn(`Image width ${options.width} exceeds max width ${serviceConfig.maxWidth}. Falling back to max width.`); + logger.warn(`Image width ${options.width} exceeds max width ${serviceConfig.maxWidth}. Falling back to max width.`); options.width = serviceConfig.maxWidth; } return options; }, - getURL(options, imageConfig) { + getURL(options, imageConfig, logger) { return `https://mysupercdn.com/${options.src}?q=${options.quality}&w=${options.width}&h=${options.height}`; }, - getHTMLAttributes(options, imageConfig) { + getHTMLAttributes(options, imageConfig, logger) { const { src, format, quality, ...attributes } = options; return { ...attributes, @@ -69,7 +69,7 @@ import type { ImageTransform, LocalImageService, AstroConfig } from "astro"; import { mySuperLibraryThatEncodesImages } from "@example/my-super-library"; const service: LocalImageService = { - getURL(options: ImageTransform, imageConfig) { + getURL(options: ImageTransform, imageConfig, logger) { const searchParams = new URLSearchParams(); searchParams.append('href', typeof options.src === "string" ? options.src : options.src.src); options.width && searchParams.append('w', options.width.toString()); @@ -77,10 +77,10 @@ const service: LocalImageService = { options.quality && searchParams.append('q', options.quality.toString()); options.format && searchParams.append('f', options.format); return `/my_custom_endpoint_that_transforms_images?${searchParams}`; - // 또는 내장된 엔드포인트를 사용하여 parsURL 및 변환 함수를 호출합니다. - // 이 함수는 `/_image?${searchParams}`를 반환합니다. + // 또는 내장 엔드포인트를 사용하면 parseURL 및 transform 함수가 호출됩니다: + // return `/_image?${searchParams}`; }, - parseURL(url: URL, imageConfig) { + parseURL(url: URL, imageConfig, logger) { const params = url.searchParams; return { src: params.get('href')!, @@ -90,14 +90,14 @@ const service: LocalImageService = { quality: params.get('q'), }; }, - async transform(inputBuffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig) { + async transform(inputBuffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig, logger) { const { buffer } = await mySuperLibraryThatEncodesImages(options); return { data: buffer, format: options.format, }; }, - getHTMLAttributes(options, imageConfig) { + getHTMLAttributes(options, imageConfig, logger) { let targetWidth = options.width; let targetHeight = options.height; if (typeof options.src === "object") { @@ -142,7 +142,7 @@ import { getConfiguredImageService, imageConfig } from "astro:assets"; import * as mime from "mrmime"; import { getImageBuffer } from "./my-custom-image-fetcher.js"; -export const GET: APIRoute = async ({ request }) => { +export const GET: APIRoute = async ({ request, logger }) => { const imageService = await getConfiguredImageService(); if (!isLocalService(imageService)) { @@ -155,6 +155,7 @@ export const GET: APIRoute = async ({ request }) => { const imageTransform = await imageService.parseURL( new URL(request.url), imageConfig, + logger, ); if (!imageTransform) { @@ -167,6 +168,7 @@ export const GET: APIRoute = async ({ request }) => { inputBuffer, imageTransform, imageConfig, + logger, ); return new Response(new Uint8Array(data), { status: 200, @@ -185,7 +187,7 @@ export const GET: APIRoute = async ({ request }) => {

      -**타입:** (options: ImageTransform, imageConfig: AstroConfig['image']) => string | Promise\
      +**타입:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => string | Promise\

      @@ -195,57 +197,71 @@ export const GET: APIRoute = async ({ request }) => { 외부 서비스의 경우 이 훅은 이미지의 최종 URL을 반환합니다. -두 서비스 유형 모두 `options`는 사용자가 `` 컴포넌트의 속성 또는 `getImage()`에 대한 옵션으로 전달한 속성입니다. +두 서비스 유형 모두 `options`는 사용자가 `` 컴포넌트의 속성 또는 `getImage()`에 대한 옵션으로 전달한 속성입니다. 이 훅은 이미지 구성과 함께 Astro v7.3.0부터는 `logger`도 받습니다. ### `parseURL()`

      -**타입:** (url: URL, imageConfig: AstroConfig['image']) => \{ src: string, [key: string]: any \} | undefined | Promise\<\{ src: string, [key: string]: any \}\> | Promise\
      +**타입:** (url: URL, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => \{ src: string, [key: string]: any \} | undefined | Promise\<\{ src: string, [key: string]: any \}\> | Promise\

      **로컬 서비스에만 필요합니다. 외부 서비스에 사용할 수 없음** -이 훅은 `getURL()`에 의해 생성된 URL을 `transform` (요청 시 렌더링 및 개발 모드에서)에 사용되는 다른 속성을 가진 객체로 다시 구문 분석합니다. 빌드 중에는 사용되지 않습니다. +이 훅은 `getURL()`에 의해 생성된 URL을 `transform`에서 사용할 수 있도록 다양한 속성을 가진 객체로 다시 파싱합니다. 이 훅은 세 가지 매개변수를 받습니다: 파싱할 URL, 이미지 구성, 그리고 Astro v7.3.0부터는 로거도 포함됩니다. + +이 훅은 요청 시 렌더링 및 개발 모드에서만 사용되며, 빌드 중에는 사용되지 않습니다. ### `transform()`

      -**타입:** (inputBuffer: Uint8Array, options: \{ src: string, [key: string]: any \}, imageConfig: AstroConfig['image']) => Promise\<\{ data: Uint8Array; format: ImageOutputFormat \}\>
      +**타입:** (inputBuffer: Uint8Array, options: \{ src: string, [key: string]: any \}, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Promise\<\{ data: Uint8Array; format: ImageOutputFormat \}\>

      **로컬 서비스에만 필요합니다. 외부 서비스에 사용할 수 없음** -이 훅은 이미지를 변환하고 반환하며 빌드 중에 호출되어 최종 자산 파일을 생성합니다. +이 훅은 이미지를 변환하고 반환하며 빌드 중에 호출되어 최종 자산 파일을 생성합니다. 이 훅은 입력 이미지, 옵션 객체, 이미지 구성, Astro v7.3.0부터는 `logger`까지 네 가지 매개변수를 받습니다. 요청 시 렌더링 및 개발 모드에서 사용자에게 적절한 MIME 유형이 제공되도록 하려면 `format`을 반환해야 합니다. +```ts +import type { LocalImageService } from 'astro'; + +const service: LocalImageService = { + // ... + async transform(inputBuffer, transform, imageConfig, logger) { + logger.warn(`Could not optimize "${transform.src}". Passing it through unchanged.`); + return { data: inputBuffer, format: 'png' }; + }, +}; +``` + ### `getHTMLAttributes()`

      -**타입:** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => Record\ | Promise\\>
      +**타입:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Record\ | Promise\\>

      **로컬 및 외부 서비스 모두 선택 사항** -이 훅은 사용자가 전달한 매개변수 (`options`)를 기반으로 이미지를 HTML로 렌더링하는 데 사용되는 모든 추가 속성을 반환합니다. +이 훅은 사용자가 전달한 매개변수(`options`)를 기반으로 이미지를 HTML로 렌더링하는 데 사용되는 모든 추가 속성을 반환합니다. 이 훅은 이미지 구성과 함께 Astro v7.3.0부터는 `logger`도 받습니다. ### `getSrcSet()`

      -**타입:** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => UnresolvedSrcSetValue[] | Promise\
      +**타입:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => UnresolvedSrcSetValue[] | Promise\

      **로컬 및 외부 서비스 모두 선택 사항입니다.** -이 훅은 `` 또는 ``의 `source`에 `srcset` 속성을 생성하기 위해 지정된 이미지의 여러 변형을 생성합니다. +이 훅은 지정된 이미지의 여러 변형을 생성합니다. 예를 들어 ``나 ``의 `source`에 대한 `srcset` 속성을 생성하기 위한 것입니다. 이 훅은 옵션 객체, 이미지 구성, Astro v7.3.0부터는 `logger`까지 세 가지 매개변수를 받습니다. 이 훅은 다음 속성을 가진 객체 배열을 반환합니다. @@ -261,13 +277,13 @@ export type UnresolvedSrcSetValue = {

      -**타입:** (options: ImageTransform, imageConfig: AstroConfig['image'] ) => ImageTransform | Promise\ +**타입:** (options: ImageTransform, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => ImageTransform | Promise\

      **로컬 및 외부 서비스 모두 선택 사항** -이 훅을 사용하면 사용자가 전달한 옵션을 검증하고 강화할 수 있습니다. 이는 기본 옵션을 설정하거나 사용자에게 매개변수가 필요함을 알리는 데 유용합니다. +이 훅을 사용하면 사용자가 전달한 옵션을 검증하고 강화할 수 있습니다. 이는 기본 옵션을 설정하거나 사용자에게 매개변수가 필요함을 알리는 데 유용합니다. 이 훅은 이미지 구성과 함께 Astro v7.3.0부터는 잘못된 옵션에 대해 사용자에게 경고하는 데 사용할 수 있는 `logger`도 받습니다. [Astro 내장 서비스에서 `validateOptions()`가 어떻게 사용되는지 확인하세요.](https://github.com/withastro/astro/blob/0ab6bad7dffd413c975ab00e545f8bc150f6a92f/packages/astro/src/assets/services/service.ts#L124) @@ -275,13 +291,13 @@ export type UnresolvedSrcSetValue = {

      -**Type:** (url: string, imageConfig: AstroConfig['image'] ) => Omit\<ImageMetadata, 'src' | 'fsPath'\> | Promise\ImageMetadata, 'src' | 'fsPath'\>\> +**타입:** (url: string, imageConfig: AstroConfig['image'], logger: AstroRuntimeLogger) => Omit\<ImageMetadata, 'src' | 'fsPath'\> | Promise\ImageMetadata, 'src' | 'fsPath'\>\>

      **로컬 및 외부 서비스 모두 선택 사항** -이 훅을 사용하면 [`inferRemoteSize()`](/ko/reference/modules/astro-assets/#inferremotesize)의 동작을 확장할 수 있습니다. 이는 이미지를 캐싱하여 네트워크 트래픽을 줄이거나, 이미지 URL에서 이미지 정보를 예측할 수 있는 경우에 유용합니다. +이 훅을 사용하면 [`inferRemoteSize()`](/ko/reference/modules/astro-assets/#inferremotesize)의 동작을 확장할 수 있습니다. 이는 이미지를 캐싱하여 네트워크 트래픽을 줄이거나, 이미지 URL에서 이미지 정보를 예측할 수 있는 경우에 유용합니다. 이 훅은 이미지 URL, 이미지 구성, Astro v7.3.0부터는 `logger`까지 세 가지 매개변수를 받습니다. ## 사용자 구성 diff --git a/src/content/docs/ko/reference/modules/astro-assets.mdx b/src/content/docs/ko/reference/modules/astro-assets.mdx index 5ee476f02f90c..2cf7a1ee7f2fa 100644 --- a/src/content/docs/ko/reference/modules/astro-assets.mdx +++ b/src/content/docs/ko/reference/modules/astro-assets.mdx @@ -642,7 +642,7 @@ import { Font } from "astro:assets"; `getImage()` 함수는 HTML이 아닌 다른 곳 (예: [API 경로](/ko/guides/endpoints/#서버-엔드포인트-api-라우트))에서 사용할 이미지를 생성하기 위한 것입니다. 또한 사용자 정의 `` 컴포넌트를 만들 수도 있습니다. -이 함수는 [Image 컴포넌트와 동일한 속성](#image-)을 가진 (`alt` 제외) 옵션 객체를 사용하며, [`GetImageResult` 객체](#getimageresult)를 반환합니다. +이 함수는 [Image 컴포넌트와 동일한 속성](#image-)을 가진 (`alt`와 `sizes` 제외) 옵션 객체를 사용하며, [`GetImageResult` 객체](#getimageresult)를 반환합니다. 다음 예시는 `
      `에 AVIF `background-image`를 생성합니다. @@ -1420,6 +1420,8 @@ import type { 이미지 변환 서비스에서 허용하는 옵션을 정의합니다. 여기에는 필수 `src` 속성, 선택적 사전 정의된 속성, 이미지 서비스에서 필요한 추가 속성이 포함됩니다. +사전 정의된 속성은 `alt`와 `sizes`를 제외하고 [`` 컴포넌트](#image-)에서 허용하는 속성과 일치합니다. 다음 속성들은 다른 타입을 사용합니다. + #### `ImageTransform.src`

      @@ -1447,64 +1449,6 @@ import type { 이미지의 높이입니다. -#### `ImageTransform.widths` - -

      - -**타입:** `number[] | undefined`
      - -

      - -이미지에 대해 생성할 너비 목록입니다. - -#### `ImageTransform.densities` - -

      - -**타입:** ``(number | `${number}x`)[] | undefined``
      - -

      - -이미지에 대해 생성할 픽셀 밀도 목록입니다. - -#### `ImageTransform.quality` - -

      - -**타입:** ImageQuality | undefined -

      - -출력 이미지에 대해 원하는 품질입니다. - -#### `ImageTransform.format` - -

      - -**타입:** ImageOutputFormat | undefined -

      - -출력 이미지에 대해 원하는 형식입니다. - -#### `ImageTransform.fit` - -

      - -**타입:** `'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | string | undefined`
      - -

      - -`object-fit` CSS 속성에 허용되는 값 목록을 정의하며, 어떤 문자열로든 확장 가능합니다. - -#### `ImageTransform.position` - -

      - -**타입:** `string | undefined`
      - -

      - -`object-position` CSS 속성의 값을 제어합니다. - ### `UnresolvedImageTransform`

      diff --git a/src/content/docs/pt-br/guides/backend/firebase.mdx b/src/content/docs/pt-br/guides/backend/firebase.mdx new file mode 100644 index 0000000000000..434354c6d0f97 --- /dev/null +++ b/src/content/docs/pt-br/guides/backend/firebase.mdx @@ -0,0 +1,932 @@ +--- +title: Firebase & Astro +description: Adicione um backend ao seu projeto com o Firebase +sidebar: + label: Firebase +type: backend +logo: firebase +stub: false +i18nReady: true +--- +import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro' +import { FileTree } from '@astrojs/starlight/components'; + + +[Firebase](https://firebase.google.com/) é uma plataforma de desenvolvimento de aplicativos que fornece banco de dados NoSQL, autenticação, inscrições em tempo real, funções e armazenamento. + +Veja nosso guia separado para [implantar na hospedagem do Firebase](/pt-br/guides/deploy/firebase/). + +## Inicializando o Firebase no Astro + +### Pré-requisitos + +- Um [projeto Firebase com um aplicativo web configurado](https://firebase.google.com/docs/web/setup). +- Um projeto Astro com [`output: 'server'` para renderização sob demanda](/pt-br/guides/on-demand-rendering/) ativado. +- Credenciais do Firebase: Você precisará de dois conjuntos de credenciais para conectar o Astro ao Firebase: + - Credenciais de aplicativo web: Essas credenciais serão usadas pelo lado do cliente do seu aplicativo. Você pode encontrá-las no console do Firebase em *Configurações do projeto > Geral*. Role para baixo até a seção **Seus aplicativos** e clique no ícone **Aplicativo Web**. + - Credenciais do projeto: Essas credenciais serão usadas pelo lado do servidor do seu aplicativo. Você pode gerá-las no console do Firebase em *Configurações do projeto > Contas de serviço > SDK Admin do Firebase > Gerar nova chave privada*. + +### Adicionando credenciais do Firebase + +Para adicionar suas credenciais do Firebase ao Astro, crie um arquivo `.env` na raiz do seu projeto com as seguintes variáveis: + +```ini title=".env" +FIREBASE_PRIVATE_KEY_ID=SUA_CHAVE_PRIVADA_ID +FIREBASE_PRIVATE_KEY=SUA_CHAVE_PRIVADA +FIREBASE_PROJECT_ID=SEU_PROJECT_ID +FIREBASE_CLIENT_EMAIL=SEU_CLIENT_EMAIL +FIREBASE_CLIENT_ID=SEU_CLIENT_ID +FIREBASE_AUTH_URI=SEU_AUTH_URI +FIREBASE_TOKEN_URI=SEU_TOKEN_URI +FIREBASE_AUTH_CERT_URL=SEU_AUTH_CERT_URL +FIREBASE_CLIENT_CERT_URL=SEU_CLIENT_CERT_URL +``` + +Agora, essas variáveis de ambiente estão disponíveis para uso no seu projeto. + +Se você gostaria de ter IntelliSense para suas variáveis de ambiente do Firebase, edite ou crie o arquivo `env.d.ts` no seu diretório `src/` e configure seus tipos: + +```ts title="src/env.d.ts" +interface ImportMetaEnv { + readonly FIREBASE_PRIVATE_KEY_ID: string; + readonly FIREBASE_PRIVATE_KEY: string; + readonly FIREBASE_PROJECT_ID: string; + readonly FIREBASE_CLIENT_EMAIL: string; + readonly FIREBASE_CLIENT_ID: string; + readonly FIREBASE_AUTH_URI: string; + readonly FIREBASE_TOKEN_URI: string; + readonly FIREBASE_AUTH_CERT_URL: string + readonly FIREBASE_CLIENT_CERT_URL: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} +``` + +:::tip +Leia mais sobre [variáveis de ambiente](/pt-br/guides/environment-variables/) e arquivos `.env` no Astro. +::: + +Seu projeto agora deve incluir estes novos arquivos: + + +- src/ + - **env.d.ts** +- **.env** +- astro.config.mjs +- package.json + + + +### Instalando dependências + +Para conectar o Astro ao Firebase, instale os seguintes pacotes usando o único comando abaixo no seu gerenciador de pacotes preferido: + +- `firebase` - o SDK do Firebase para o lado do cliente +- `firebase-admin` - o SDK Admin do Firebase para o lado do servidor + + + + ```shell + npm install firebase firebase-admin + ``` + + + ```shell + pnpm add firebase firebase-admin + ``` + + + ```shell + yarn add firebase firebase-admin + ``` + + + +Em seguida, crie uma pasta chamada `firebase` no diretório `src/` e adicione dois novos arquivos a essa pasta: `client.ts` e `server.ts`. + +Em `client.ts`, adicione o seguinte código para inicializar o Firebase no cliente usando as credenciais do seu aplicativo web e o pacote `firebase`: + +```ts title="src/firebase/client.ts" +import { initializeApp } from "firebase/app"; + +const firebaseConfig = { + apiKey: "minha-chave-api-publica", + authDomain: "meu-dominio-autenticacao", + projectId: "meu-id-projeto", + storageBucket: "meu-bucket-armazenamento", + messagingSenderId: "meu-id-remetente", + appId: "meu-id-aplicacao", +}; + +export const app = initializeApp(firebaseConfig); +``` + +:::note +Lembre-se de substituir o objeto `firebaseConfig` com suas próprias credenciais de aplicativo web. +::: + +Em `server.ts`, adicione o seguinte código para inicializar o Firebase no servidor usando as credenciais do seu projeto e o pacote `firebase-admin`: + +```ts title="src/firebase/server.ts" +import type { ServiceAccount } from "firebase-admin"; +import { initializeApp, cert, getApps } from "firebase-admin/app"; + +const activeApps = getApps(); +const serviceAccount = { + type: "service_account", + project_id: import.meta.env.FIREBASE_PROJECT_ID, + private_key_id: import.meta.env.FIREBASE_PRIVATE_KEY_ID, + private_key: import.meta.env.FIREBASE_PRIVATE_KEY, + client_email: import.meta.env.FIREBASE_CLIENT_EMAIL, + client_id: import.meta.env.FIREBASE_CLIENT_ID, + auth_uri: import.meta.env.FIREBASE_AUTH_URI, + token_uri: import.meta.env.FIREBASE_TOKEN_URI, + auth_provider_x509_cert_url: import.meta.env.FIREBASE_AUTH_CERT_URL, + client_x509_cert_url: import.meta.env.FIREBASE_CLIENT_CERT_URL, +}; + +const initApp = () => { + if (import.meta.env.PROD) { + console.info('Ambiente de produção detectado. Usando a conta de serviço padrão.') + // Usar configuração padrão nas funções do firebase. Já deve estar injetada no servidor pelo Firebase. + return initializeApp() + } + console.info('Carregando a conta de serviço das variáveis de ambiente.') + return initializeApp({ + credential: cert(serviceAccount as ServiceAccount) + }) +} + +export const app = activeApps.length === 0 ? initApp() : activeApps[0]; +``` + +:::note +Lembre-se de substituir o objeto `serviceAccount` com suas próprias credenciais do projeto. +::: + +Por fim, seu projeto agora deve incluir estes novos arquivos: + + +- src + - env.d.ts + - firebase + - **client.ts** + - **server.ts** +- .env +- astro.config.mjs +- package.json + + +## Adicionando autenticação com o Firebase + +### Pré-requisitos + +- Um projeto Astro [inicializado com o Firebase](#inicializando-o-firebase-no-astro). +- Um projeto Firebase com autenticação de e-mail/senha ativada no console do Firebase sob o método *Authentication > Sign-in*. + +### Criando endpoints de servidor para auth + +A autenticação do Firebase no Astro exige os três seguintes [endpoints de servidor do Astro](/pt-br/guides/endpoints/): + +- `GET /api/auth/entrar` - para entrar com um usuário +- `GET /api/auth/sair` - para desconectar um usuário +- `POST /api/auth/registrar` - para registrar um usuário + +Crie três endpoints relacionados à autenticação em um novo diretório `src/pages/api/auth/`: `entrar.ts`, `sair.ts` e `registrar.ts`. + +`entrar.ts` contém o código para autenticar um usuário usando o Firebase: + +```ts title="src/pages/api/auth/entrar.ts" +import type { APIRoute } from "astro"; +import { app } from "../../../firebase/server"; +import { getAuth } from "firebase-admin/auth"; + +export const GET: APIRoute = async ({ request, cookies, redirect }) => { + const auth = getAuth(app); + + /* Obter token dos cabeçalhos da requisição */ + const tokenId = request.headers.get("Authorization")?.split("Bearer ")[1]; + if (!tokenId) { + return new Response( + "Token não encontrado", + { status: 401 } + ); + } + + /* Verificar token de id */ + try { + await auth.verifyIdToken(tokenId); + } catch (erro) { + return new Response( + "Token inválido", + { status: 401 } + ); + } + + /* Criar e definir cookie de sessão */ + const fiveDays = 60 * 60 * 24 * 5 * 1000; + const cookieSessao = await auth.createSessionCookie(tokenId, { + expiresIn: fiveDays, + }); + + cookies.set("__session", cookieSessao, { + path: "/", + }); + + return redirect("/dashboard"); +}; +``` + +:::caution +O Firebase permite apenas o uso de [um cookie, e ele deve se chamar `__session`](https://firebase.google.com/docs/hosting/manage-cache#using_cookies). Quaisquer outros cookies enviados pelo cliente não serão visíveis para sua aplicação. +::: + +:::note +Esta é uma implementação básica do endpoint de entrada. Você pode adicionar mais lógica a este endpoint para atender às suas necessidades. +::: + +`sair.ts` contém o código para desconectar um usuário excluindo o cookie de sessão: + +```ts title="src/pages/api/auth/sair.ts" +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async ({ redirect, cookies }) => { + cookies.delete("__session", { + path: "/", + }); + return redirect("/entrar"); +}; +``` + +:::note +Esta é uma implementação básica do endpoint de saída. Você pode adicionar mais lógica a este endpoint para atender às suas necessidades. +::: + +`registrar.ts` contém o código para registrar um usuário usando o Firebase: + +```ts title="src/pages/api/auth/registrar.ts" +import type { APIRoute } from "astro"; +import { getAuth } from "firebase-admin/auth"; +import { app } from "../../../firebase/server"; + +export const POST: APIRoute = async ({ request, redirect }) => { + const auth = getAuth(app); + + /* Obter dados do formulário */ + const dadosFormulario = await request.formData(); + const email = dadosFormulario.get("email")?.toString(); + const senha = dadosFormulario.get("senha")?.toString(); + const nome = dadosFormulario.get("nome")?.toString(); + + if (!email || !senha || !nome) { + return new Response( + "Dados do formulário ausentes", + { status: 400 } + ); + } + + /* Criar usuário */ + try { + await auth.createUser({ + email, + password: senha, + displayName: nome, + }); + } catch (error: any) { + return new Response( + "Ocorreu um erro ao criar o usuário", + { status: 400 } + ); + } + return redirect("/entrar"); +}; +``` + +:::note +Esta é uma implementação básica do endpoint de registro. Você pode adicionar mais lógica a este endpoint para atender às suas necessidades. +::: + +Após criar os endpoints de servidor para autenticação, o diretório do seu projeto agora deve incluir estes novos arquivos: + + +- src + - env.d.ts + - firebase + - client.ts + - server.ts + - pages + - api + - auth + - **entrar.ts** + - **sair.ts** + - **registrar.ts** +- .env +- astro.config.mjs +- package.json + + +### Criando páginas + +Crie as páginas que usarão os endpoints do Firebase: + +- `src/pages/registrar` - conterá um formulário para registrar um usuário +- `src/pages/entrar` - conterá um formulário para autenticar um usuário +- `src/pages/dashboard` - conterá um painel que só pode ser acessado por usuários autenticados + +O exemplo `src/pages/registrar.astro` abaixo inclui um formulário que enviará uma requisição `POST` para o endpoint `/api/auth/registrar`. Este endpoint criará um novo usuário usando os dados do formulário e então redirecionará o usuário para a página `/entrar`. + +```astro title="src/pages/registrar.astro" +--- +import Layout from "../layouts/Layout.astro"; +--- + + +

      Registrar

      +

      Já possui uma conta? Entrar

      +
      + + + + + + + +
      + +``` + +`src/pages/entrar.astro` usa o app do Firebase Server para verificar o cookie de sessão do usuário. Se o usuário estiver autenticado, a página redirecionará o usuário para a página `/dashboard`. + +A página de exemplo abaixo contém um formulário que enviará uma requisição `POST` para o endpoint `/api/auth/entrar` com o token de ID gerado pelo aplicativo cliente do Firebase. + +O endpoint verificará o token de ID e criará um novo cookie de sessão para o usuário. Em seguida, o endpoint redirecionará o usuário para a página `/dashboard`. + +```astro title="src/pages/entrar.astro" +--- +import { app } from "../firebase/server"; +import { getAuth } from "firebase-admin/auth"; +import Layout from "../layouts/Layout.astro"; + +/* Verificar se o usuário está autenticado */ +const auth = getAuth(app); +if (Astro.cookies.has("__session")) { + const cookieSessao = Astro.cookies.get("__session")!.value; + const cookieDecodificado = await auth.verifySessionCookie(cookieSessao); + if (cookieDecodificado) { + return Astro.redirect("/dashboard"); + } +} +--- + + +

      Entrar

      +

      Novo por aqui? Criar uma conta

      +
      + + + + + +
      +
      + +``` + +`src/pages/dashboard.astro` verificará o cookie de sessão do usuário usando o aplicativo de servidor do Firebase. Se o usuário não estiver autenticado, a página redirecionará o usuário para a página `/entrar`. + +A página de exemplo abaixo exibe o nome do usuário e um botão para sair. Clicar no botão enviará uma requisição `GET` para o endpoint `/api/auth/sair`. + +O endpoint excluirá o cookie de sessão do usuário e redirecionará o usuário para a página `/entrar`. + +```astro title="src/pages/dashboard.astro" +--- +import { app } from "../firebase/server"; +import { getAuth } from "firebase-admin/auth"; +import Layout from "../layouts/Layout.astro"; + +const auth = getAuth(app); + +/* Verificar sessão atual */ +if (!Astro.cookies.has("__session")) { + return Astro.redirect("/entrar"); +} +const cookieSessao = Astro.cookies.get("__session")!.value; +const cookieDecodificado = await auth.verifySessionCookie(cookieSessao); +const usuario = await auth.getUser(cookieDecodificado.uid); + +if (!usuario) { + return Astro.redirect("/entrar"); +} +--- + + +

      Bem-vindo(a) {usuario.displayName}

      +

      Ficamos felizes em ver você aqui

      +
      + +
      +
      +``` + +### Adicionando provedores OAuth + +Para adicionar provedores OAuth ao seu aplicativo, você precisa ativá-los no console do Firebase. + +No console do Firebase, vá para a seção **Authentication** e clique na aba **Método de login**. Em seguida, clique no botão **Adicionar novo fornecedor** e ative os provedores que deseja usar. + +O exemplo abaixo usa o provedor do **Google**. + +Edite a página `entrar.astro` para adicionar: +- um botão para entrar com o Google abaixo do formulário existente +- um ouvinte de evento no botão para manipular o processo de login no ` +``` + +Quando clicado, o botão de entrar com o Google abrirá uma janela pop-up para fazer login com o Google. Assim que o usuário entrar, ele enviará uma requisição `POST` para o endpoint `/api/auth/entrar` com o token de ID gerado pelo provedor OAuth. + +O endpoint verificará o token de ID e criará um novo cookie de sessão para o usuário. Em seguida, o endpoint redirecionará o usuário para a página `/dashboard`. + +## Conectando ao banco de dados Firestore + +### Pré-requisitos + +- Um projeto Astro inicializado com o Firebase conforme descrito na seção [Inicializando o Firebase no Astro](#inicializando-o-firebase-no-astro). + +- Um projeto Firebase com um banco de dados Firestore. Você pode seguir a [documentação do Firebase para criar um novo projeto e configurar um banco de dados Firestore](https://firebase.google.com/docs/firestore/quickstart?hl=pt-br). + +Nesta receita, a coleção do Firestore será chamada de **amigos** e conterá documentos com os seguintes campos: + +- `id`: gerado automaticamente pelo Firestore +- `nome`: um campo do tipo string +- `idade`: um campo do tipo number +- `eMelhorAmigo`: um campo do tipo boolean + +### Criando os endpoints do servidor + +Crie dois novos arquivos em um novo diretório `src/pages/api/amigos/`: `index.ts` e `[id].ts`. Eles criarão dois endpoints de servidor para interagir com o banco de dados Firestore das seguintes maneiras: + +- `POST /api/amigos`: para criar um novo documento na coleção de amigos. +- `POST /api/amigos/:id`: para atualizar um documento na coleção de amigos. +- `DELETE /api/amigos/:id`: para excluir um documento na coleção de amigos. + +`index.ts` conterá o código para criar um novo documento na coleção de amigos: + +```ts title="src/pages/api/amigos/index.ts" +import type { APIRoute } from "astro"; +import { app } from "../../../firebase/server"; +import { getFirestore } from "firebase-admin/firestore"; + +export const POST: APIRoute = async ({ request, redirect }) => { + const dadosFormulario = await request.formData(); + const nome = dadosFormulario.get("nome")?.toString(); + const idade = dadosFormulario.get("idade")?.toString(); + const eMelhorAmigo = dadosFormulario.get("eMelhorAmigo") === "on"; + + if (!nome || !idade) { + return new Response("Campos obrigatórios ausentes", { + status: 400, + }); + } + try { + const bd = getFirestore(app); + const refAmigos = bd.collection("amigos"); + await refAmigos.add({ + nome, + idade: parseInt(idade), + eMelhorAmigo, + }); + } catch (erro) { + return new Response("Ocorreu um erro", { + status: 500, + }); + } + return redirect("/dashboard"); +}; +``` + +:::note +Esta é uma implementação básica do endpoint `amigos`. Você pode adicionar mais lógica a este endpoint para atender às suas necessidades. +::: + +`[id].ts` conterá o código para atualizar e excluir um documento na coleção de amigos: + +```ts title="src/pages/api/amigos/[id].ts" +import type { APIRoute } from "astro"; +import { app } from "../../../firebase/server"; +import { getFirestore } from "firebase-admin/firestore"; + +const bd = getFirestore(app); +const refAmigos = bd.collection("amigos"); + +export const POST: APIRoute = async ({ params, redirect, request }) => { + const dadosFormulario = await request.formData(); + const nome = dadosFormulario.get("nome")?.toString(); + const idade = dadosFormulario.get("idade")?.toString(); + const eMelhorAmigo = dadosFormulario.get("eMelhorAmigo") === "on"; + + if (!nome || !idade) { + return new Response("Campos obrigatórios ausentes", { + status: 400, + }); + } + + if (!params.id) { + return new Response("Amigo não encontrado", { + status: 404, + }); + } + + try { + await refAmigos.doc(params.id).update({ + nome, + idade: parseInt(idade), + eMelhorAmigo, + }); + } catch (erro) { + return new Response("Ocorreu um erro", { + status: 500, + }); + } + return redirect("/dashboard"); +}; + +export const DELETE: APIRoute = async ({ params, redirect }) => { + if (!params.id) { + return new Response("Amigo não encontrado", { + status: 404, + }); + } + + try { + await refAmigos.doc(params.id).delete(); + } catch (erro) { + return new Response("Ocorreu um erro", { + status: 500, + }); + } + return redirect("/dashboard"); +}; +``` + +:::note +Esta é uma implementação básica do endpoint `amigos/:id`. Você pode adicionar mais lógica a este endpoint para atender às suas necessidades. +::: + +Após criar os endpoints de servidor para o Firestore, o diretório do seu projeto agora deve incluir estes novos arquivos: + + +- src + - env.d.ts + - firebase + - client.ts + - server.ts + - pages + - api + - amigos + - **index.ts** + - **[id].ts** +- .env +- astro.config.mjs +- package.json + + +### Criando páginas + +Crie as páginas que usarão os endpoints do Firestore: + +- `src/pages/adicionar.astro` - conterá um formulário para adicionar um novo amigo. +- `src/pages/editar/[id].astro` - conterá um formulário para editar um amigo e um botão para excluir um amigo. +- `src/pages/amigo/[id].astro` - conterá os detalhes de um amigo. +- `src/pages/dashboard.astro` - exibirá uma lista de amigos. + +#### Adicionar um novo registro + +O exemplo `src/pages/adicionar.astro` abaixo inclui um formulário que enviará uma requisição `POST` para o endpoint `/api/amigos`. Este endpoint criará um novo amigo usando os dados do formulário e então redirecionará o usuário para a página `/dashboard`. + +```astro title="src/pages/adicionar.astro" +--- +import Layout from "../layouts/Layout.astro"; +--- + + +

      Adicionar um novo amigo

      +
      + + + + + + + +
      +
      +``` + +#### Editar ou Excluir um registro + +`src/pages/editar/[id].astro` conterá um formulário para editar os dados de um amigo e um botão para excluir um amigo. Ao enviar, esta página enviará uma requisição `POST` para o endpoint `/api/amigos/:id` para atualizar os dados de um amigo. + +Se o usuário clicar no botão de excluir, esta página enviará uma requisição `DELETE` para o endpoint `/api/amigos/:id` para excluir o amigo. + +```astro title="src/pages/editar/[id].astro" +--- +import Layout from "../../layouts/Layout.astro"; +import { app } from "../../firebase/server"; +import { getFirestore } from "firebase-admin/firestore"; + +interface Amigo { + name: string; + idade: number; + eMelhorAmigo: boolean; +} + +const { id } = Astro.params; + +if (!id) { + return Astro.redirect("/404"); +} + +const bd = getFirestore(app); +const refAmigos = bd.collection("amigos"); +const snapshotAmigo = await refAmigos.doc(id).get(); + +if (!snapshotAmigo.exists) { + return Astro.redirect("/404"); +} + +const amigo = snapshotAmigo.data() as Amigo; +--- + + +

      Editar {amigo.nome}

      +

      Aqui você pode editar ou excluir os dados do seu amigo.

      +
      + + + + + + + +
      + +
      + +``` + +#### Exibir um registro individual + +`src/pages/amigo/[id].astro` exibirá os detalhes de um amigo. + +```astro title="src/pages/amigo/[id].astro" +--- +import Layout from "../../layouts/Layout.astro"; +import { app } from "../../firebase/server"; +import { getFirestore } from "firebase-admin/firestore"; + +interface Amigo { + nome: string; + idade: number; + eMelhorAmigo: boolean; +} + +const { id } = Astro.params; + +if (!id) { + return Astro.redirect("/404"); +} + +const bd = getFirestore(app); +const refAmigos = bd.collection("amigos"); +const snapshotAmigo = await refAmigos.doc(id).get(); + +if (!snapshotAmigo.exists) { + return Astro.redirect("/404"); +} + +const amigo = snapshotAmigo.data() as Amigo; +--- + + +

      {amigo.nome}

      +

      Idade: {amigo.idade}

      +

      É melhor amigo: {amigo.eMelhorAmigo ? "Sim" : "Não"}

      +
      +``` + +#### Exibir uma lista de registros com um botão de edição + +Por fim, `src/pages/dashboard.astro` exibirá uma lista de amigos. Cada amigo terá um link para a página de detalhes e um botão de edição que redirecionará o usuário para a página de edição. + +```astro title="src/pages/dashboard.astro" +--- +import { app } from "../firebase/server"; +import { getFirestore } from "firebase-admin/firestore"; +import Layout from "../layouts/Layout.astro"; + +interface Amigo { + id: string; + nome: string; + idade: number; + eMelhorAmigo: boolean; +} + +const bd = getFirestore(app); +const refAmigos = bd.collection("amigos"); +const amigosSnapshot = await refAmigos.get(); +const amigos = amigosSnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), +})) as Amigo[]; +--- + + +

      Amigos

      +
        + { + amigos.map((amigo) => ( +
      • + {amigo.nome} + ({amigo.idade}) + {amigo.eMelhorAmigo ? "Melhor Amigo" : "Amigo"} + Editar +
      • + )) + } +
      +
      + +``` + +Após criar todas as páginas, você deve ter a seguinte estrutura de arquivos: + + +- src + - env.d.ts + - firebase + - client.ts + - server.ts + - pages + - dashboard.astro + - adicionar.astro + - editar + - [id].astro + - amigo + - [id].astro + - api + - amigos + - index.ts + - [id].ts +- .env +- astro.config.mjs +- package.json + + +## Recursos da Comunidade + +- [Exemplo de app SSR com Astro e Firebase](https://github.com/kevinzunigacuellar/astro-firebase) +- [Usando Firebase Realtime Database no Astro com Vue: Um Guia Passo a Passo](https://www.launchfa.st/blog/vue-astro-firebase-realtime-database) diff --git a/src/content/docs/pt-br/guides/backend/prisma-postgres.mdx b/src/content/docs/pt-br/guides/backend/prisma-postgres.mdx new file mode 100644 index 0000000000000..8ca3d91d0c2c3 --- /dev/null +++ b/src/content/docs/pt-br/guides/backend/prisma-postgres.mdx @@ -0,0 +1,191 @@ +--- +title: Prisma Postgres & Astro +description: Adicione um banco de dados Postgres serverless ao seu projeto Astro com o Prisma Postgres +sidebar: + label: Prisma Postgres +type: backend +logo: 'prisma-postgres' +stub: false +i18nReady: true +--- + +import ReadMore from '~/components/ReadMore.astro'; + +[Prisma Postgres](https://www.prisma.io/) é um banco de dados Postgres totalmente gerenciado e serverless construído para aplicativos web modernos. + +## Conectar com o Prisma ORM (Recomendado) + +O [Prisma ORM](https://www.prisma.io/orm) é a forma recomendada de se conectar ao seu banco de dados Prisma Postgres. Ele oferece consultas com segurança de tipos (type-safe), migrações e desempenho global. + +### Pré-requisitos +- Um projeto Astro com um adaptador instalado para ativar a [renderização sob demanda (SSR)](/pt-br/guides/on-demand-rendering/). + +### Instalar dependências e inicializar o Prisma + +Execute os seguintes comandos para instalar as dependências necessárias do Prisma: + +```bash +npm install prisma tsx --save-dev +npm install @prisma/adapter-pg @prisma/client +``` + +Uma vez instalado, inicialize o Prisma no seu projeto com o seguinte comando: + +```bash +npx prisma init --db --output ./generated +``` + +Você precisará responder a algumas perguntas ao configurar seu banco de dados Prisma Postgres. Selecione a região mais próxima da sua localização e um nome memorável para o seu banco de dados, como "Meu Projeto Astro". + +Isso criará: +- Um diretório `prisma/` com um arquivo `schema.prisma` +- Um arquivo `.env` com a variável `DATABASE_URL` já configurada + +### Definir um Modelo + +Mesmo se você não precisar de modelos de dados específicos ainda, o Prisma requer pelo menos um modelo no schema para gerar um cliente e aplicar migrações. + +O exemplo a seguir define um modelo `Postagem` como modelo provisório. Adicione o modelo ao seu schema para começar. Você pode excluí-lo ou substituí-lo com segurança mais tarde por modelos que reflitam seus dados reais. + +```prisma title="prisma/schema.prisma" ins={11-16} +generator client { + provider = "prisma-client" + output = "./generated" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Postagem { + id Int @id @default(autoincrement()) + titulo String + conteudo String? + publicada Boolean @default(false) +} +``` + +Saiba mais sobre como configurar a sua instalação do Prisma ORM na [referência do schema do Prisma](https://www.prisma.io/docs/concepts/components/prisma-schema). + +### Gerar cliente + +Execute o seguinte comando para gerar o Prisma Client a partir do seu schema: + +```bash +npx prisma generate +``` + +### Gerar arquivos de migração + +Execute o seguinte comando para criar as tabelas do banco de dados e gerar o Prisma Client a partir do seu schema. Isso também criará um diretório `prisma/migrations/` com arquivos do histórico de migrações. + +```bash +npx prisma migrate dev --name init +``` + +### Criar um Prisma Client + +Dentro de `/src/lib`, crie um arquivo `prisma.ts`. Este arquivo inicializará e exportará a instância do seu Prisma Client para que você possa consultar seu banco de dados em todo o seu projeto Astro. + +```typescript title="src/lib/prisma.ts" +import { PrismaPg } from '@prisma/adapter-pg'; +import { PrismaClient } from '../../prisma/generated/client'; + +const stringConexao = import.meta.env.DATABASE_URL; +const adaptador = new PrismaPg({ connectionString: stringConexao }); +const prisma = new PrismaClient({ adapter: adaptador }); + +export default prisma; +``` + +### Consultando e exibindo dados + +O exemplo a seguir mostra a busca apenas das suas postagens publicadas com o Prisma Client ordenadas por `id`, e então exibindo os títulos e o conteúdo da postagem no seu modelo do Astro: + +```astro title="src/pages/postagens.astro" {2, 4-7} +--- +import prisma from '../lib/prisma'; + +const postagens = await prisma.post.findMany({ + where: { publicado: true }, + orderBy: { id: 'desc' } +}); +--- + + + + Postagens Publicadas + + +

      Postagens Publicadas

      +
        + {postagens.map((post) => ( +
      • +

        {post.titulo}

        + {post.conteudo &&

        {post.conteudo}

        } +
      • + ))} +
      + + +``` + +A melhor prática é lidar com consultas em uma rota de API. Para mais informações sobre como usar o Prisma ORM no seu projeto Astro, veja o [Guia Astro + Prisma ORM](https://www.prisma.io/docs/guides/frameworks/astro). + +## Conectar com Outros ORMs e Bibliotecas + +Você pode se conectar ao Prisma Postgres via TCP direto usando qualquer outro ORM, biblioteca de banco de dados ou ferramenta de sua escolha. Crie uma string de conexão direta no seu Console Prisma para começar. + +### Pré-requisitos +- Um projeto Astro com um adaptador instalado para ativar a [renderização sob demanda (SSR)](/pt-br/guides/on-demand-rendering/). +- Um banco de dados [Prisma Postgres](https://pris.ly/ppg) com uma string de conexão com TCP ativado + +### Instalar dependências + +Este exemplo usa [`pg`, um cliente PostgreSQL para Node.js](https://github.com/brianc/node-postgres) para fazer uma conexão TCP direta. + +Execute o seguinte comando para instalar o pacote `pg`: + +```bash +npm install pg +``` + +### Consultar o cliente do seu banco de dados + +Forneça sua string de conexão para o cliente `pg` para se comunicar com o seu servidor SQL e buscar dados do seu banco de dados. + +O exemplo a seguir de criação de uma tabela e inserção de dados pode ser usado para validar sua URL de consulta e conexão TCP: + +```astro title="src/pages/index.astro" {2-19} +--- +import { Client } from 'pg'; +const client = new Client({ + connectionString: import.meta.env.DATABASE_URL, + ssl: { rejectUnauthorized: false } +}); +await client.connect(); + +await client.query(` + CREATE TABLE IF NOT EXISTS postagens ( + id SERIAL PRIMARY KEY, + titulo TEXT UNIQUE, + conteudo TEXT + ); + + INSERT INTO postagens (titulo, conteudo) + VALUES ('Olá', 'Mundo') + ON CONFLICT (titulo) DO NOTHING; +`); + +const { rows } = await client.query('SELECT * FROM postagens'); +await client.end(); +--- + +

      Postagens

      +

      {rows[0].titulo}: {rows[0].conteudo}

      +``` + +## Recursos Oficiais + +- [Guia Astro + Prisma ORM](https://www.prisma.io/docs/guides/frameworks/astro) diff --git a/src/content/docs/ru/guides/data-fetching.mdx b/src/content/docs/ru/guides/data-fetching.mdx new file mode 100644 index 0000000000000..4f268fe401583 --- /dev/null +++ b/src/content/docs/ru/guides/data-fetching.mdx @@ -0,0 +1,110 @@ +--- +title: Получение данных +description: Узнайте, как получать удалённые данные в Astro с помощью fetch API. +i18nReady: true +--- + +Файлы `.astro` могут получать удалённые данные, которые помогут вам генерировать страницы. + +## `fetch()` в Astro + +Все [компоненты Astro](/ru/basics/astro-components/) имеют доступ к [глобальной функции `fetch()`](https://developer.mozilla.org/ru/docs/Web/API/Window/fetch) в скрипте компонента, чтобы выполнять HTTP-запросы к API по полному URL (например, `https://example.com/api`). +Кроме того, с помощью [`new URL("/api", Astro.url)`](/ru/reference/api-reference/#url) можно построить URL к страницам и эндпойнтам вашего проекта, которые рендерятся на сервере по запросу. + +Такой вызов fetch будет выполнен во время сборки, и данные будут доступны шаблону компонента для генерации динамического HTML. Если включён режим [SSR](/ru/guides/on-demand-rendering/), все вызовы fetch будут выполняться во время работы сервера. + +💡 Используйте [**`await` верхнего уровня**](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/await#top_level_await) в скрипте компонента Astro. + +💡 Передавайте полученные данные как пропсы и в компоненты Astro, и в компоненты фреймворков. + +```astro /await fetch\\(.*?\\);/ +--- +// src/components/User.astro +import Contact from "../components/Contact.jsx"; +import Location from "../components/Location.astro"; + +const response = await fetch("https://randomuser.me/api/"); +const data = await response.json(); +const randomUser = data.results[0]; +--- + +

      Пользователь

      +

      {randomUser.name.first} {randomUser.name.last}

      + + + + +``` + +:::note +Помните: все данные в компонентах Astro запрашиваются в момент рендеринга компонента. + +Развёрнутый сайт Astro запрашивает данные **один раз, во время сборки**. В режиме разработки запросы данных происходят при обновлении компонентов. Если данные нужно многократно запрашивать на стороне клиента, используйте в компоненте Astro [компонент фреймворка](/ru/guides/framework-components/) или [клиентский скрипт](/ru/guides/client-side-scripts/). +::: + +## `fetch()` в компонентах фреймворков + +Функция `fetch()` также глобально доступна в любых [компонентах фреймворков](/ru/guides/framework-components/): + +```tsx title="src/components/Movies.tsx" /await fetch\\(.*?\\)/ +import type { FunctionalComponent } from 'preact'; + +const data = await fetch('https://example.com/movies.json').then((response) => response.json()); + +// Компоненты, рендерящиеся во время сборки, выводят логи в CLI. +// При рендеринге с директивой `client:*` они выводят логи и в консоль браузера. +console.log(data); + +const Movies: FunctionalComponent = () => { + // Выводим результат на страницу + return
      {JSON.stringify(data)}
      ; +}; + +export default Movies; +``` + +## GraphQL-запросы + +Astro также может использовать `fetch()` для обращения к GraphQL-серверу с любым корректным GraphQL-запросом. + +```astro title="src/components/Film.astro" "await fetch" +--- +const response = await fetch( + "https://swapi-graphql.netlify.app/.netlify/functions/index", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query: ` + query getFilm ($id:ID!) { + film(id: $id) { + title + releaseDate + } + } + `, + variables: { + id: "ZmlsbXM6MQ==", + }, + }), + } +); + + +const json = await response.json(); +const { film } = json.data; +--- +

      Получаем информацию о фильме «Звёздные войны: Новая надежда»

      +

      Название: {film.title}

      +

      Год: {film.releaseDate}

      +``` + +## Получение данных из headless CMS + +Компоненты Astro могут получать данные из вашей любимой CMS и рендерить их как содержимое страницы. С помощью [динамических маршрутов](/ru/guides/routing/#dynamic-routes) компоненты могут даже генерировать страницы на основе содержимого CMS. + +Подробности об интеграции Astro с headless CMS, включая Storyblok, Contentful и WordPress, смотрите в наших [руководствах по CMS](/ru/guides/cms/). + +## Ресурсы сообщества + +- [Создание fullstack-приложения с Astro + GraphQL](https://robkendal.co.uk/blog/how-to-build-astro-site-with-graphql/) diff --git a/src/content/docs/ru/guides/server-islands.mdx b/src/content/docs/ru/guides/server-islands.mdx new file mode 100644 index 0000000000000..06eca045eceb1 --- /dev/null +++ b/src/content/docs/ru/guides/server-islands.mdx @@ -0,0 +1,125 @@ +--- +title: Серверные островки +description: Сочетайте высокопроизводительный статический HTML с динамическим контентом, отрендеренным на сервере. +i18nReady: true +--- + +Серверные островки позволяют рендерить по запросу динамические или персонализированные «островки» по отдельности, не жертвуя производительностью остальной части страницы. + +Это значит, что посетитель раньше увидит самые важные части страницы, а основной контент можно будет кешировать агрессивнее, что ускорит работу сайта. + +## Компоненты серверных островков + +Серверный островок — это обычный [компонент Astro](/ru/basics/astro-components/), рендерящийся на сервере, которому указано отложить рендеринг до тех пор, пока его содержимое не станет доступно. + +Страница будет отрендерена сразу с указанным [резервным контентом в качестве заглушки](#резервный-контент-серверного-островка). Затем собственное содержимое компонента запрашивается на клиенте и отображается, когда оно готово. + +Установив [адаптер](/ru/guides/on-demand-rendering/#серверные-адаптеры) для выполнения отложенного рендеринга, добавьте [директиву `server:defer`](/ru/reference/directives-reference/#server-directives) любому компоненту на странице, чтобы превратить его в отдельный островок: + +```astro title="src/pages/index.astro" "server:defer" +--- +import Avatar from '../components/Avatar.astro'; +--- + +``` + +Такие компоненты могут делать [всё, что обычно доступно на странице, рендерящейся по запросу](/ru/guides/on-demand-rendering/#возможности-рендеринга-по-запросу) через адаптер, — например, запрашивать контент и обращаться к кукам: + +```astro title="src/components/Avatar.astro" +--- +import { getUserAvatar } from '../sessions'; +const userSession = Astro.cookies.get('session'); +const avatarURL = await getUserAvatar(userSession); +--- +Аватар пользователя +``` + +### Передача пропсов серверным островкам + +Пропсы, передаваемые компонентам серверных островков, должны быть [сериализуемыми](https://developer.mozilla.org/ru/docs/Glossary/Serialization): то есть их можно преобразовать в формат, пригодный для передачи по сети или для хранения. Кроме того, Astro сериализует не все типы сериализуемых структур данных. Поэтому на то, что можно передать серверному островку в качестве пропсов, есть ограничения. + +В частности, компонентам с директивой `server:defer` нельзя передавать функции, так как они не сериализуются. Объекты с циклическими ссылками также не сериализуемы. + +Поддерживаются следующие типы пропсов: +простой объект, `number`, `string`, `Array`, `Map`, `Set`, `RegExp`, `Date`, `BigInt`, `URL`, `Uint8Array`, `Uint16Array`, `Uint32Array` и `Infinity` + +## Резервный контент серверного островка + +Используя атрибут `server:defer` на компоненте для откладывания его рендеринга, вы можете «подставить» контент загрузки по умолчанию через встроенный именованный слот `"fallback"`. + +Резервный контент отрендерится вместе с остальной страницей при её первоначальной загрузке и будет заменён содержимым компонента, когда оно станет доступно. + +Чтобы добавить резервный контент, укажите `slot="fallback"` на дочернем элементе (другом компоненте или HTML-элементе), переданном компоненту серверного островка: + +```astro +--- +import Avatar from '../components/Avatar.astro'; +import GenericAvatar from '../components/GenericAvatar.astro'; +--- + + + +``` + +Резервным контентом могут быть, например: + +- Обобщённый аватар вместо аватара пользователя. +- UI-заглушки, например собственные сообщения. +- Индикаторы загрузки, например спиннеры. + +## Как это работает + +Реализация серверных островков происходит в основном на этапе сборки, когда содержимое компонента подменяется небольшим скриптом. + +Каждый островок с директивой `server:defer` выделяется в собственный специальный маршрут, который этот скрипт запрашивает во время выполнения. При сборке сайта Astro пропустит компонент и вставит на его место скрипт, а также контент, помеченный `slot="fallback"`. + +Когда страница загрузится в браузере, эти компоненты будут запрошены со специального эндпоинта, который отрендерит их и вернёт HTML. Благодаря этому пользователи мгновенно увидят самые важные части страницы. Резервный контент будет виден непродолжительное время, пока не загрузятся динамические островки. + +Каждый островок загружается независимо от остальных. Это значит, что более медленный островок не задержит появление остального персонализированного контента. + +Этот паттерн рендеринга создавался переносимым. Он не зависит от какой-либо серверной инфраструктуры, поэтому будет работать с любым хостингом — от сервера Node.js в Docker-контейнере до бессерверного провайдера на ваш выбор. + +## Кеширование + +Данные для серверных островков запрашиваются через `GET`-запрос, а пропсы передаются в виде зашифрованной строки в query-параметрах URL. Это позволяет кешировать данные с помощью [HTTP-заголовка `Cache-Control`](https://developer.mozilla.org/ru/docs/Web/HTTP/Reference/Headers/Cache-Control), используя его стандартные директивы. + +Однако [браузер ограничивает длину URL максимумом в 2048 байт](https://chromium.googlesource.com/chromium/src/+/master/docs/security/url_display_guidelines/url_display_guidelines.md#url-length) из практических соображений и во избежание проблем типа «отказ в обслуживании». Если из-за строки запроса URL превышает этот лимит, Astro вместо этого отправит `POST`-запрос со всеми пропсами в теле. + +`POST`-запросы не кешируются браузерами, поскольку используются для отправки данных и могли бы привести к проблемам с целостностью данных или безопасностью. Поэтому существующая логика кеширования в вашем проекте перестанет работать. По возможности передавайте серверным островкам только необходимые пропсы и не отправляйте целые объекты данных и массивы, чтобы строка запроса оставалась небольшой. + +## Доступ к URL страницы в серверном островке + +В большинстве случаев компонент серверного островка может получить информацию о странице, которая его рендерит, через [передачу пропсов](/ru/basics/astro-components/#пропсы-компонента), как в обычных компонентах. + +Однако серверные островки выполняются в собственном изолированном контексте вне запроса страницы. `Astro.url` и `Astro.request.url` в компоненте серверного островка возвращают URL вида `/_server-islands/Avatar`, а не URL текущей страницы в браузере. Кроме того, при предварительном рендеринге страницы у вас не будет доступа, например, к query-параметрам, чтобы передать их как пропсы. + +Чтобы получить информацию из URL страницы, проверьте заголовок [Referer](https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Referer) — он содержит адрес страницы, загружающей островок в браузере: + +```astro +--- +const referer = Astro.request.headers.get("Referer"); + +if (!referer) { + throw new Error("Referer header is missing"); +} + +const url = new URL(referer); +const productId = url.searchParams.get("product"); +--- +``` + +## Повторное использование ключа шифрования + +Astro использует [криптографию](https://developer.mozilla.org/ru/docs/Glossary/Cryptography) для шифрования пропсов, передаваемых серверным островкам, защищая чувствительные данные от случайного раскрытия. Шифрование опирается на новый случайный ключ, который генерируется при каждой сборке и встраивается в серверный бандл. + +Большинство хостингов автоматически позаботятся о синхронизации фронтенда и бэкенда. Однако постоянный ключ шифрования может понадобиться, если вы используете скользящие развёртывания (rolling deployments), мультирегиональный хостинг или CDN, кеширующий страницы с серверными островками. + +В окружениях со скользящими развёртываниями (например, Kubernetes), где фронтенд-ресурсы (шифрующие пропсы) и бэкенд-функции (расшифровывающие их) могут временно использовать разные ключи, а также когда CDN всё ещё отдаёт страницы, собранные со старым ключом, зашифрованные пропсы серверного островка расшифровать не удастся. + +В таких ситуациях сгенерируйте с помощью Astro CLI многоразовый закодированный ключ шифрования и задайте его переменной окружения в сборочном окружении: + +```shell +astro create-key +``` + +Используйте полученное значение для настройки переменной окружения `ASTRO_KEY` (например, в файле `.env`) и добавьте её в настройки сборки CI/CD или хостинга. Так в собираемом бандле всегда будет использоваться один и тот же ключ, а шифрование и расшифровка останутся синхронизированными. diff --git a/src/data/contributors.json b/src/data/contributors.json index 098bfa9fe7858..c7cca67cfcd96 100644 --- a/src/data/contributors.json +++ b/src/data/contributors.json @@ -123,25 +123,25 @@ "id": 29927270, "login": "Chrissdroid" }, - { - "id": 45708948, - "login": "kecrily" - }, { "id": 70939128, "login": "jp-knj" }, { - "id": 11063910, - "login": "Fryuni" + "id": 45708948, + "login": "kecrily" + }, + { + "id": 3019731, + "login": "Princesseuh" }, { "id": 13069, "login": "kyosuke" }, { - "id": 3019731, - "login": "Princesseuh" + "id": 11063910, + "login": "Fryuni" }, { "id": 10626596, @@ -156,13 +156,17 @@ "login": "HiDeoo" }, { - "id": 31162600, - "login": "agustinmulet" + "id": 59626670, + "login": "angelmarfil" }, { "id": 188426, "login": "jonathantneal" }, + { + "id": 31162600, + "login": "agustinmulet" + }, { "id": 88548999, "login": "at-the-vr" @@ -175,6 +179,10 @@ "id": 11240579, "login": "Genteure" }, + { + "id": 326143391, + "login": "mixto" + }, { "id": 25723446, "login": "casungo" @@ -187,14 +195,14 @@ "id": 271088549, "login": "yan-thomas" }, - { - "id": 34116392, - "login": "bluwy" - }, { "id": 12275019, "login": "Egpereira" }, + { + "id": 34116392, + "login": "bluwy" + }, { "id": 7118177, "login": "natemoo-re" @@ -203,25 +211,25 @@ "id": 11237366, "login": "Hanawa02" }, - { - "id": 28299972, - "login": "aFuzzyBear" - }, { "id": 64152685, "login": "Yusaku01" }, { - "id": 113022468, - "login": "fkatsuhiro" + "id": 28299972, + "login": "aFuzzyBear" + }, + { + "id": 81974850, + "login": "MoustaphaDev" }, { "id": 25883220, "login": "JuanPabloDiaz" }, { - "id": 81974850, - "login": "MoustaphaDev" + "id": 113022468, + "login": "fkatsuhiro" }, { "id": 7684330, @@ -231,10 +239,6 @@ "id": 49518790, "login": "pioupia" }, - { - "id": 59626670, - "login": "angelmarfil" - }, { "id": 36927158, "login": "maxchang3" @@ -263,10 +267,6 @@ "id": 103585995, "login": "bjohansebas" }, - { - "id": 16712703, - "login": "JuanM04" - }, { "id": 213306, "login": "ascorbic" @@ -275,6 +275,10 @@ "id": 669326, "login": "mrienstra" }, + { + "id": 16712703, + "login": "JuanM04" + }, { "id": 20650404, "login": "VoxelMC" @@ -287,29 +291,29 @@ "id": 47194884, "login": "oscarxdev" }, - { - "id": 11986753, - "login": "kimulaco" - }, { "id": 74939915, "login": "glopzel" }, + { + "id": 11986753, + "login": "kimulaco" + }, { "id": 11766500, "login": "antonyfaris" }, { - "id": 3756185, - "login": "afucher" + "id": 150704902, + "login": "randomguy-2650" }, { "id": 1425259, "login": "shuuji3" }, { - "id": 150704902, - "login": "randomguy-2650" + "id": 3756185, + "login": "afucher" }, { "id": 11061182, @@ -335,29 +339,29 @@ "id": 101558384, "login": "luoingly" }, - { - "id": 12174733, - "login": "itskitto" - }, { "id": 45965090, "login": "alexanderniebuhr" }, { - "id": 69170106, - "login": "lilnasy" + "id": 12174733, + "login": "itskitto" }, { - "id": 42184309, - "login": "asgoshawk" + "id": 58832428, + "login": "erbierc" }, { "id": 77632836, "login": "clemenzi" }, { - "id": 58832428, - "login": "erbierc" + "id": 42184309, + "login": "asgoshawk" + }, + { + "id": 69170106, + "login": "lilnasy" }, { "id": 110926935, @@ -412,41 +416,45 @@ "login": "teinett" }, { - "id": 114303361, - "login": "ryuapp" - }, - { - "id": 44868357, - "login": "codersjj" - }, - { - "id": 175721795, - "login": "rafaelcoelhox" + "id": 990216, + "login": "leoj3n" }, { "id": 141838499, "login": "cristhian-fs" }, - { - "id": 990216, - "login": "leoj3n" - }, { "id": 19967622, "login": "BryceRussell" }, + { + "id": 66965600, + "login": "louisescher" + }, { "id": 115520730, "login": "OliverSpeir" }, { - "id": 66965600, - "login": "louisescher" + "id": 114303361, + "login": "ryuapp" + }, + { + "id": 44868357, + "login": "codersjj" + }, + { + "id": 175721795, + "login": "rafaelcoelhox" }, { "id": 89195061, "login": "anaxite" }, + { + "id": 703248, + "login": "agriffard" + }, { "id": 7007253, "login": "mottox2" @@ -467,21 +475,21 @@ "id": 44106297, "login": "hkbertoson" }, - { - "id": 57179957, - "login": "yeonjulee1005" - }, { "id": 33442948, "login": "apatel369" }, { - "id": 703248, - "login": "agriffard" + "id": 57179957, + "login": "yeonjulee1005" }, { - "id": 92775570, - "login": "HashCookie" + "id": 25793187, + "login": "arisa-fukuzaki" + }, + { + "id": 243528248, + "login": "bulebrainbrand" }, { "id": 42414986, @@ -508,44 +516,44 @@ "login": "crutchcorn" }, { - "id": 22087604, - "login": "clearlyTHUYDOAN" + "id": 46154381, + "login": "magnum-zx" }, { "id": 74556046, "login": "fhiromasa" }, { - "id": 46154381, - "login": "magnum-zx" - }, - { - "id": 58094796, - "login": "Jothsa" + "id": 22087604, + "login": "clearlyTHUYDOAN" }, { - "id": 139560930, - "login": "Panelinio" + "id": 66757451, + "login": "avilyre" }, { - "id": 67210629, - "login": "jdwilkin4" + "id": 14830190, + "login": "caioferrarezi" }, { "id": 92606530, "login": "Elib27" }, { - "id": 25793187, - "login": "arisa-fukuzaki" + "id": 67210629, + "login": "jdwilkin4" }, { - "id": 66757451, - "login": "avilyre" + "id": 58094796, + "login": "Jothsa" }, { - "id": 14830190, - "login": "caioferrarezi" + "id": 139560930, + "login": "Panelinio" + }, + { + "id": 92775570, + "login": "HashCookie" }, { "id": 37566594, @@ -575,6 +583,18 @@ "id": 2342458, "login": "palmiak" }, + { + "id": 17983739, + "login": "imbant" + }, + { + "id": 190183925, + "login": "ankddev" + }, + { + "id": 16765690, + "login": "bandantonio" + }, { "id": 34824645, "login": "staticWagomU" @@ -583,13 +603,21 @@ "id": 65327974, "login": "manchan4869" }, + { + "id": 100040151, + "login": "BassamXYZ" + }, + { + "id": 37726261, + "login": "wtchnm" + }, { "id": 51922004, "login": "Maxframe" }, { - "id": 17983739, - "login": "imbant" + "id": 10464497, + "login": "garysassano" }, { "id": 1110792, @@ -603,6 +631,10 @@ "id": 78506637, "login": "gacek1123" }, + { + "id": 12196684, + "login": "yamotech" + }, { "id": 5608239, "login": "chalkygames123" @@ -611,42 +643,6 @@ "id": 168486811, "login": "sigma7863" }, - { - "id": 1091472, - "login": "chenxsan" - }, - { - "id": 19380403, - "login": "ralacerda" - }, - { - "id": 63650415, - "login": "RafidMuhymin" - }, - { - "id": 100040151, - "login": "BassamXYZ" - }, - { - "id": 10464497, - "login": "garysassano" - }, - { - "id": 37726261, - "login": "wtchnm" - }, - { - "id": 16765690, - "login": "bandantonio" - }, - { - "id": 3282350, - "login": "olets" - }, - { - "id": 190183925, - "login": "ankddev" - }, { "id": 49127376, "login": "IgorKowalczyk" @@ -659,6 +655,10 @@ "id": 79452224, "login": "Kenzo-Wada" }, + { + "id": 3282350, + "login": "olets" + }, { "id": 15347255, "login": "lorenzolewis" @@ -676,44 +676,68 @@ "login": "tinymachine" }, { - "id": 26341224, - "login": "mogeko" + "id": 1091472, + "login": "chenxsan" }, { - "id": 73933669, - "login": "nermalcat69" + "id": 19380403, + "login": "ralacerda" }, { - "id": 77222233, - "login": "NightFeather0615" + "id": 63650415, + "login": "RafidMuhymin" + }, + { + "id": 58347116, + "login": "Pukimaa" }, { "id": 81039882, "login": "Njong392" }, { - "id": 58347116, - "login": "Pukimaa" + "id": 26341224, + "login": "mogeko" }, { - "id": 3241026, - "login": "mantaroh" + "id": 77222233, + "login": "NightFeather0615" }, { - "id": 51779800, - "login": "ktym4a" + "id": 73933669, + "login": "nermalcat69" }, { - "id": 75212478, - "login": "dorasans" + "id": 111561, + "login": "tordans" + }, + { + "id": 16623919, + "login": "shupianx" + }, + { + "id": 148322070, + "login": "divyeshb13" }, { "id": 49699333, "login": "dependabot[bot]" }, { - "id": 16623919, - "login": "shupianx" + "id": 75212478, + "login": "dorasans" + }, + { + "id": 51779800, + "login": "ktym4a" + }, + { + "id": 9054858, + "login": "lostra01" + }, + { + "id": 3241026, + "login": "mantaroh" }, { "id": 35761035, @@ -739,33 +763,25 @@ "id": 362261, "login": "chriswburke" }, - { - "id": 13678847, - "login": "mitian233" - }, - { - "id": 9054858, - "login": "lostra01" - }, - { - "id": 148322070, - "login": "divyeshb13" - }, { "id": 30044630, "login": "AkashRajpurohit" }, { - "id": 87678248, - "login": "David-Large" + "id": 86967271, + "login": "coding-in-public" }, { "id": 81493003, "login": "danielcuque" }, { - "id": 86967271, - "login": "coding-in-public" + "id": 29717818, + "login": "danielmlr" + }, + { + "id": 87678248, + "login": "David-Large" }, { "id": 77161808, @@ -828,8 +844,8 @@ "login": "simonswiss" }, { - "id": 111561, - "login": "tordans" + "id": 119287439, + "login": "silvecor" }, { "id": 69125074, @@ -843,10 +859,6 @@ "id": 18255987, "login": "coderfee" }, - { - "id": 243528248, - "login": "bulebrainbrand" - }, { "id": 87353286, "login": "vedxp" @@ -884,12 +896,8 @@ "login": "BlackdestinyXX" }, { - "id": 17054057, - "login": "thepassle" - }, - { - "id": 119287439, - "login": "silvecor" + "id": 78465651, + "login": "newtoallofthis123" }, { "id": 53004404, @@ -943,6 +951,10 @@ "id": 162127610, "login": "xingwangzhe" }, + { + "id": 13678847, + "login": "mitian233" + }, { "id": 20949060, "login": "bengeois" @@ -979,10 +991,6 @@ "id": 92310163, "login": "davidumoru" }, - { - "id": 29717818, - "login": "danielmlr" - }, { "id": 7117993, "login": "cravend" @@ -1007,14 +1015,14 @@ "id": 867257, "login": "Because789" }, - { - "id": 62723180, - "login": "ahmed-n-abdeltwab" - }, { "id": 26602940, "login": "0xflotus" }, + { + "id": 62723180, + "login": "ahmed-n-abdeltwab" + }, { "id": 66678395, "login": "DevRohit06" @@ -1023,6 +1031,10 @@ "id": 7355835, "login": "radenpioneer" }, + { + "id": 17054057, + "login": "thepassle" + }, { "id": 28926450, "login": "nicdun" @@ -1044,48 +1056,40 @@ "login": "mhdcodes" }, { - "id": 36402166, - "login": "debiru" - }, - { - "id": 91272406, - "login": "baevm" - }, - { - "id": 90469240, - "login": "MartinFerret" + "id": 24359130, + "login": "swift502" }, { - "id": 61264139, - "login": "kannansuresh" + "id": 1029022, + "login": "sunapi386" }, { - "id": 8854718, - "login": "kanadgupta" + "id": 34408108, + "login": "Je12emy" }, { "id": 18559798, "login": "jdbruxelles" }, { - "id": 34408108, - "login": "Je12emy" + "id": 36402166, + "login": "debiru" }, { - "id": 1029022, - "login": "sunapi386" + "id": 91272406, + "login": "baevm" }, { - "id": 24359130, - "login": "swift502" + "id": 90469240, + "login": "MartinFerret" }, { - "id": 78465651, - "login": "newtoallofthis123" + "id": 8854718, + "login": "kanadgupta" }, { - "id": 299173, - "login": "lukemcdonald" + "id": 61264139, + "login": "kannansuresh" }, { "id": 50741, @@ -1095,6 +1099,10 @@ "id": 18482346, "login": "Rolanddoda" }, + { + "id": 16581093, + "login": "vitoriapena" + }, { "id": 5978625, "login": "rolginroman" @@ -1148,13 +1156,17 @@ "login": "XinChou16" }, { - "id": 193320883, - "login": "MareStare" + "id": 3964466, + "login": "aaronlamz" }, { "id": 129096443, "login": "MaxTheTurtle0" }, + { + "id": 137305666, + "login": "1t1sCooL" + }, { "id": 37586974, "login": "mingXta" @@ -1216,13 +1228,17 @@ "login": "RobertAKARobin" }, { - "id": 29157111, - "login": "lhz960904" + "id": 91918142, + "login": "vivitt" }, { "id": 31998110, "login": "lstephensca" }, + { + "id": 287639636, + "login": "mrchatam" + }, { "id": 190968675, "login": "my-astro" @@ -1284,12 +1300,8 @@ "login": "isyuah" }, { - "id": 16581093, - "login": "vitoriapena" - }, - { - "id": 91918142, - "login": "vivitt" + "id": 58039870, + "login": "wahidrizka" }, { "id": 82119938, @@ -1352,8 +1364,8 @@ "login": "kikonavarro" }, { - "id": 58281730, - "login": "CheukTsai" + "id": 29157111, + "login": "lhz960904" }, { "id": 1045274, @@ -1420,8 +1432,8 @@ "login": "ogabrielp" }, { - "id": 3964466, - "login": "aaronlamz" + "id": 830515, + "login": "gaeulbyul" }, { "id": 51356696, @@ -1488,12 +1500,12 @@ "login": "calebeby" }, { - "id": 88730883, - "login": "Jhon-H" + "id": 58281730, + "login": "CheukTsai" }, { - "id": 29053796, - "login": "jcha0713" + "id": 1514154, + "login": "neotherapper" }, { "id": 62016742, @@ -1556,12 +1568,12 @@ "login": "Trombach" }, { - "id": 830515, - "login": "gaeulbyul" + "id": 299173, + "login": "lukemcdonald" }, { - "id": 1514154, - "login": "neotherapper" + "id": 193320883, + "login": "MareStare" }, { "id": 68320771, @@ -1605,7 +1617,7 @@ }, { "id": 140852203, - "login": "vandorsx" + "login": "ilyisx" }, { "id": 7950094, @@ -1624,23 +1636,19 @@ "login": "jazzypants1989" }, { - "id": 3164034, - "login": "notjb" - }, - { - "id": 52315048, - "login": "3w36zj6" + "id": 88730883, + "login": "Jhon-H" }, { - "id": 1002694, - "login": "aaronkai" + "id": 29053796, + "login": "jcha0713" }, { - "id": 6192554, - "login": "arafays" + "id": 3164034, + "login": "notjb" }, { - "id": 61620817, - "login": "alfawal" + "id": 52315048, + "login": "3w36zj6" } ] diff --git a/src/data/logos.ts b/src/data/logos.ts index e178b569b912b..0013b6de63a37 100644 --- a/src/data/logos.ts +++ b/src/data/logos.ts @@ -66,6 +66,7 @@ export const logos = LogoCheck({ hygraph: { file: 'hygraph.svg', padding: '.1em .125em .1em .1em' }, image: { file: 'astro-image.svg', padding: '.1875em' }, imagekit: { file: 'imagekit.svg', padding: '.15em' }, + ishosting: { file: 'ishosting.svg', padding: '.1625em' }, jekyll: { file: 'jekyll.png', padding: '.1em .05em 0' }, jekyllpad: { file: 'jekyllpad.svg', padding: '0.2em' }, keystatic: { file: 'keystatic.svg', padding: '0' }, diff --git a/wrangler.jsonc b/wrangler.jsonc index bf35f3ad8926f..3b24286b8d08e 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -8,6 +8,7 @@ "binding": "ASSETS", "not_found_handling": "none", }, + "previews": {}, "preview_urls": true, "workers_dev": false, } diff --git a/wrangler.lunaria.jsonc b/wrangler.lunaria.jsonc index ae5a60831786f..f55c6c0c638ad 100644 --- a/wrangler.lunaria.jsonc +++ b/wrangler.lunaria.jsonc @@ -6,4 +6,5 @@ "directory": "./dist/lunaria", "html_handling": "auto-trailing-slash", }, + "previews": {}, }