Text Generation
Transformers
Safetensors
GGUF
English
code
code-generation
multimodal
llama
conversational
Instructions to use mrsandip/Alfa-Code with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use mrsandip/Alfa-Code with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="mrsandip/Alfa-Code")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("mrsandip/Alfa-Code", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use mrsandip/Alfa-Code with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf mrsandip/Alfa-Code:Q4_0 # Run inference directly in the terminal: llama cli -hf mrsandip/Alfa-Code:Q4_0
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf mrsandip/Alfa-Code:Q4_0 # Run inference directly in the terminal: llama cli -hf mrsandip/Alfa-Code:Q4_0
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf mrsandip/Alfa-Code:Q4_0 # Run inference directly in the terminal: ./llama-cli -hf mrsandip/Alfa-Code:Q4_0
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf mrsandip/Alfa-Code:Q4_0 # Run inference directly in the terminal: ./build/bin/llama-cli -hf mrsandip/Alfa-Code:Q4_0
Use Docker
docker model run hf.co/mrsandip/Alfa-Code:Q4_0
- LM Studio
- Jan
- vLLM
How to use mrsandip/Alfa-Code with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "mrsandip/Alfa-Code" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "mrsandip/Alfa-Code", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/mrsandip/Alfa-Code:Q4_0
- SGLang
How to use mrsandip/Alfa-Code with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "mrsandip/Alfa-Code" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "mrsandip/Alfa-Code", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "mrsandip/Alfa-Code" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "mrsandip/Alfa-Code", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use mrsandip/Alfa-Code with Ollama:
ollama run hf.co/mrsandip/Alfa-Code:Q4_0
- Unsloth Desktop
- Docker Model Runner
How to use mrsandip/Alfa-Code with Docker Model Runner:
docker model run hf.co/mrsandip/Alfa-Code:Q4_0
- Lemonade
How to use mrsandip/Alfa-Code with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull mrsandip/Alfa-Code:Q4_0
Run and chat with the model
lemonade run user.Alfa-Code-Q4_0
List all available models
lemonade list
- Atomic Chat
Update (part 25)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- e-commerce/node_modules/next-intl-swc-plugin-extractor/LICENSE +21 -0
- e-commerce/node_modules/next-intl-swc-plugin-extractor/README.md +5 -0
- e-commerce/node_modules/next-intl-swc-plugin-extractor/package.json +26 -0
- e-commerce/node_modules/next-intl-swc-plugin-extractor/target/wasm32-wasip1/release/swc_plugin_extractor.wasm +3 -0
- e-commerce/node_modules/next-intl/LICENSE +21 -0
- e-commerce/node_modules/next-intl/README.md +99 -0
- e-commerce/node_modules/next-intl/config.d.ts +4 -0
- e-commerce/node_modules/next-intl/dist/cjs/development/BuiltInPoCodec-B6w-kmTx.cjs +34 -0
- e-commerce/node_modules/next-intl/dist/cjs/development/plugin.cjs +1637 -0
- e-commerce/node_modules/next-intl/dist/esm/development/config.js +5 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor.js +2 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/ExtractionCompiler.js +38 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/CatalogLocales.js +84 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/CatalogManager.js +422 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/CatalogPersister.js +82 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/SaveScheduler.js +85 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalogLoader.js +118 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractMessages.js +22 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractionLoader.js +28 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractor/LRUCache.js +30 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractor/MessageExtractor.js +75 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/format/ExtractorCodec.js +8 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/format/codecs/BuiltInPoCodec.js +22 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/format/index.js +59 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/normalizeExtractorConfig.js +70 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/source/SourceFileFilter.js +29 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/source/SourceFileScanner.js +31 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/source/SourceFileWatcher.js +132 -0
- e-commerce/node_modules/next-intl/dist/esm/development/extractor/utils.js +60 -0
- e-commerce/node_modules/next-intl/dist/esm/development/index.react-client.js +4 -0
- e-commerce/node_modules/next-intl/dist/esm/development/index.react-server.js +9 -0
- e-commerce/node_modules/next-intl/dist/esm/development/middleware.js +1 -0
- e-commerce/node_modules/next-intl/dist/esm/development/middleware/getAlternateLinksHeaderValue.js +94 -0
- e-commerce/node_modules/next-intl/dist/esm/development/middleware/middleware.js +172 -0
- e-commerce/node_modules/next-intl/dist/esm/development/middleware/resolveLocale.js +129 -0
- e-commerce/node_modules/next-intl/dist/esm/development/middleware/syncCookie.js +42 -0
- e-commerce/node_modules/next-intl/dist/esm/development/middleware/utils.js +202 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation.react-client.js +1 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation.react-server.js +1 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-client/createNavigation.js +79 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-client/useBasePathname.js +36 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-server/createNavigation.js +22 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-server/getServerLocale.js +12 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/BaseLink.js +64 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/createSharedNavigationFns.js +105 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/syncLocaleCookie.js +41 -0
- e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/utils.js +169 -0
- e-commerce/node_modules/next-intl/dist/esm/development/plugin.js +1 -0
- e-commerce/node_modules/next-intl/dist/esm/development/plugin/config.js +10 -0
- e-commerce/node_modules/next-intl/dist/esm/development/plugin/createNextIntlPlugin.js +52 -0
e-commerce/node_modules/next-intl-swc-plugin-extractor/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 Jan Amann
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
e-commerce/node_modules/next-intl-swc-plugin-extractor/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# next-intl-swc-plugin-extractor
|
| 2 |
+
|
| 3 |
+
SWC plugin for extracting inline messages.
|
| 4 |
+
|
| 5 |
+
**Important:** This package is only intended for internal use and it's API may change at any time.
|
e-commerce/node_modules/next-intl-swc-plugin-extractor/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "next-intl-swc-plugin-extractor",
|
| 3 |
+
"version": "4.14.9",
|
| 4 |
+
"sideEffects": false,
|
| 5 |
+
"author": "Jan Amann <jan@amann.work>",
|
| 6 |
+
"contributors": [
|
| 7 |
+
"강동윤 <kdy1997.dev@gmail.com>"
|
| 8 |
+
],
|
| 9 |
+
"description": "SWC plugin for extracting inline messages.",
|
| 10 |
+
"license": "MIT",
|
| 11 |
+
"homepage": "https://next-intl.dev",
|
| 12 |
+
"repository": {
|
| 13 |
+
"type": "git",
|
| 14 |
+
"url": "git+https://github.com/amannn/next-intl.git"
|
| 15 |
+
},
|
| 16 |
+
"scripts": {
|
| 17 |
+
"prepublishOnly": "turbo build",
|
| 18 |
+
"test": "cargo test --release",
|
| 19 |
+
"build": "cargo build --release --target wasm32-wasip1 --target-dir target"
|
| 20 |
+
},
|
| 21 |
+
"main": "target/wasm32-wasip1/release/swc_plugin_extractor.wasm",
|
| 22 |
+
"files": [
|
| 23 |
+
"target/wasm32-wasip1/release/swc_plugin_extractor.wasm"
|
| 24 |
+
],
|
| 25 |
+
"gitHead": "b9fc98a814957b9c97894c7b57151215ac39afa6"
|
| 26 |
+
}
|
e-commerce/node_modules/next-intl-swc-plugin-extractor/target/wasm32-wasip1/release/swc_plugin_extractor.wasm
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe1a1051266f0e28e75d123b88d9bd170a7ea26c568ef9e2fcf5e577ba19af40
|
| 3 |
+
size 1269847
|
e-commerce/node_modules/next-intl/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 Jan Amann
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
e-commerce/node_modules/next-intl/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<p align="center">
|
| 2 |
+
<br>
|
| 3 |
+
<a href="https://next-intl.dev">
|
| 4 |
+
<picture>
|
| 5 |
+
<source media="(prefers-color-scheme: dark)" srcset="media/logo-dark-mode.svg">
|
| 6 |
+
<source media="(prefers-color-scheme: light)" srcset="media/logo.svg">
|
| 7 |
+
<img alt="next-intl" src="media/logo.svg" width="600">
|
| 8 |
+
</picture>
|
| 9 |
+
</a>
|
| 10 |
+
<br>
|
| 11 |
+
<br>
|
| 12 |
+
</p>
|
| 13 |
+
|
| 14 |
+
Internationalization for Next.js.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
|
| 18 |
+
Internationalization (i18n) is an essential part of the user experience, therefore `next-intl` gives you all the parts you need to get language nuances right.
|
| 19 |
+
|
| 20 |
+
- 🌟 **ICU message syntax**: Localize your messages with interpolation, cardinal & ordinal plurals, enum-based label selection and rich text.
|
| 21 |
+
- 📅 **Dates, times & numbers**: Apply appropriate formatting without worrying about server/client differences like time zones.
|
| 22 |
+
- ✅ **Type-safe**: Speed up development with autocompletion for message keys and catch typos early with compile-time checks.
|
| 23 |
+
- 💡 **Hooks-based API**: Learn a single API that can be used across your code base to turn translations into plain strings or rich text.
|
| 24 |
+
- 🚀 **Next.js-native and performance-obsessed**: App Router, Server Components, static rendering—pick the right tool for the right job, next-intl works everywhere.
|
| 25 |
+
- 🌍 **Internationalized routing**: Provide unique pathnames per language and optionally localize pathnames for search engine optimization.
|
| 26 |
+
|
| 27 |
+
[→ Read the docs](https://next-intl.dev)
|
| 28 |
+
|
| 29 |
+
## What does it look like?
|
| 30 |
+
|
| 31 |
+
```jsx
|
| 32 |
+
// UserProfile.tsx
|
| 33 |
+
import {useTranslations} from 'next-intl';
|
| 34 |
+
|
| 35 |
+
export default function UserProfile({user}) {
|
| 36 |
+
const t = useTranslations('UserProfile');
|
| 37 |
+
|
| 38 |
+
return (
|
| 39 |
+
<section>
|
| 40 |
+
<h1>{t('title', {firstName: user.firstName})}</h1>
|
| 41 |
+
<p>{t('membership', {memberSince: user.memberSince})}</p>
|
| 42 |
+
<p>{t('followers', {count: user.numFollowers})}</p>
|
| 43 |
+
</section>
|
| 44 |
+
);
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
```js
|
| 49 |
+
// en.json
|
| 50 |
+
{
|
| 51 |
+
"UserProfile": {
|
| 52 |
+
"title": "{firstName}'s profile",
|
| 53 |
+
"membership": "Member since {memberSince, date, short}",
|
| 54 |
+
"followers": "{count, plural, ↵
|
| 55 |
+
=0 {No followers yet} ↵
|
| 56 |
+
=1 {One follower} ↵
|
| 57 |
+
other {# followers} ↵
|
| 58 |
+
}"
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
## Lint your messages
|
| 64 |
+
|
| 65 |
+
As an app grows, messages may drift and become inconsistent.
|
| 66 |
+
|
| 67 |
+
A companion tool, [`eloqnt/cli`](https://cli.eloqnt.dev/docs), catches this by analyzing your source code and messages statically:
|
| 68 |
+
|
| 69 |
+
```console
|
| 70 |
+
$ eloqnt lint
|
| 71 |
+
|
| 72 |
+
messages/de.json
|
| 73 |
+
│
|
| 74 |
+
│ "UserProfile.title": "Dein Profil"
|
| 75 |
+
│ ─┬──────────
|
| 76 |
+
│ ╰─ Inconsistent ICU arguments: missing {firstName} (inconsistent-args)
|
| 77 |
+
│
|
| 78 |
+
│ "UserProfile.membership": "Member since {memberSince, date, short}"
|
| 79 |
+
│ ─┬──────────────────────
|
| 80 |
+
│ ╰─ Missing translation for de (missing-translation)
|
| 81 |
+
|
| 82 |
+
✗ 1 error
|
| 83 |
+
! 1 warning
|
| 84 |
+
|
| 85 |
+
→ Rule details: https://cli.eloqnt.dev/docs/lint-rules/<rule>
|
| 86 |
+
→ Run `eloqnt translate` to fill in 1 missing translation
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
It can optionally also fill in missing translations with [`eloqnt translate`](https://cli.eloqnt.dev/docs/cli/translate), using your source code as context.
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
<div align="center">
|
| 94 |
+
<a href="https://next-intl.dev/redirect?href=https://crowdin.com" target="_blank">
|
| 95 |
+
<img width="350" src="media/partner.svg" alt="Crowdin logo">
|
| 96 |
+
</a>
|
| 97 |
+
<br>
|
| 98 |
+
<p>Hosted on <a href="https://vercel.com?utm_source=next-intl&utm_campaign=oss">Vercel</a></p>
|
| 99 |
+
</div>
|
e-commerce/node_modules/next-intl/config.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Needed for projects with `moduleResolution: 'node'`
|
| 2 |
+
import config from './dist/types/config';
|
| 3 |
+
|
| 4 |
+
export default config;
|
e-commerce/node_modules/next-intl/dist/cjs/development/BuiltInPoCodec-B6w-kmTx.cjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use strict';
|
| 2 |
+
|
| 3 |
+
var createPoCodec = require('@eloqnt/format-po');
|
| 4 |
+
|
| 5 |
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
| 6 |
+
|
| 7 |
+
var createPoCodec__default = /*#__PURE__*/_interopDefaultCompat(createPoCodec);
|
| 8 |
+
|
| 9 |
+
// Derived from `@eloqnt/config`, so next-intl and eloqnt pass identical
|
| 10 |
+
// options to codecs.
|
| 11 |
+
|
| 12 |
+
function defineCodec(factory) {
|
| 13 |
+
return factory;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
// `@eloqnt/format-po`, guarded so catalogs in the previous key-based layout
|
| 17 |
+
// fail loudly. Custom codecs referencing the package directly opt out.
|
| 18 |
+
var BuiltInPoCodec = defineCodec(() => {
|
| 19 |
+
const codec = createPoCodec__default.default();
|
| 20 |
+
function assertMigrated(content) {
|
| 21 |
+
if (content.includes('X-Crowdin-SourceKey: msgstr')) {
|
| 22 |
+
throw new Error('This catalog uses the previous PO layout of next-intl, which stored message keys in `msgid`. The PO format now follows the gettext convention: the source text in `msgid` and the message key in `msgctxt`. Please migrate your catalogs, or use a custom codec to keep the previous layout: https://github.com/amannn/next-intl/pull/2393');
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
return {
|
| 26 |
+
...codec,
|
| 27 |
+
decode(content, context) {
|
| 28 |
+
assertMigrated(content);
|
| 29 |
+
return codec.decode(content, context);
|
| 30 |
+
}
|
| 31 |
+
};
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
exports.default = BuiltInPoCodec;
|
e-commerce/node_modules/next-intl/dist/cjs/development/plugin.cjs
ADDED
|
@@ -0,0 +1,1637 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use strict';
|
| 2 |
+
|
| 3 |
+
var fs$1 = require('fs/promises');
|
| 4 |
+
var path = require('path');
|
| 5 |
+
var module$1 = require('module');
|
| 6 |
+
var url = require('url');
|
| 7 |
+
var watcher = require('@parcel/watcher');
|
| 8 |
+
var fs = require('fs');
|
| 9 |
+
var core = require('@swc/core');
|
| 10 |
+
|
| 11 |
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
| 12 |
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
| 13 |
+
|
| 14 |
+
var fs__default$1 = /*#__PURE__*/_interopDefaultCompat(fs$1);
|
| 15 |
+
var path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
| 16 |
+
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
| 17 |
+
|
| 18 |
+
function formatMessage(message) {
|
| 19 |
+
return `\n[next-intl] ${message}\n`;
|
| 20 |
+
}
|
| 21 |
+
function throwError(message) {
|
| 22 |
+
throw new Error(formatMessage(message));
|
| 23 |
+
}
|
| 24 |
+
function warn(message) {
|
| 25 |
+
console.warn(formatMessage(message));
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/**
|
| 29 |
+
* Returns a function that runs the provided callback only once per process.
|
| 30 |
+
* Next.js can call the config multiple times - this ensures we only run once.
|
| 31 |
+
* Uses an environment variable to track execution across config loads.
|
| 32 |
+
*/
|
| 33 |
+
function once(namespace) {
|
| 34 |
+
return function runOnce(fn) {
|
| 35 |
+
if (process.env[namespace] === '1') {
|
| 36 |
+
return;
|
| 37 |
+
}
|
| 38 |
+
process.env[namespace] = '1';
|
| 39 |
+
fn();
|
| 40 |
+
};
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
function stripTrailingSlash(dirPath) {
|
| 44 |
+
if (dirPath.endsWith('/')) {
|
| 45 |
+
return dirPath.slice(0, -1);
|
| 46 |
+
} else {
|
| 47 |
+
return dirPath;
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
function normalizeMessagesCatalogPaths(messagesPath) {
|
| 51 |
+
const rawPaths = Array.isArray(messagesPath) ? messagesPath : [messagesPath];
|
| 52 |
+
return rawPaths.map(dirPath => stripTrailingSlash(String(dirPath).trim())).filter(dirPath => dirPath.length > 0);
|
| 53 |
+
}
|
| 54 |
+
function normalizeExtractorConfig(input) {
|
| 55 |
+
if (input.messages == null) {
|
| 56 |
+
throwError('`messages` is required when extracting messages.');
|
| 57 |
+
}
|
| 58 |
+
const extract = input.extract;
|
| 59 |
+
let extractPath;
|
| 60 |
+
let sourceLocale;
|
| 61 |
+
if (extract !== undefined && extract !== true) {
|
| 62 |
+
if (extract.sourceLocale) {
|
| 63 |
+
warn('`extract.sourceLocale` is deprecated in favor of `messages.sourceLocale`.');
|
| 64 |
+
sourceLocale = extract.sourceLocale;
|
| 65 |
+
}
|
| 66 |
+
if (extract.path) {
|
| 67 |
+
extractPath = stripTrailingSlash(extract.path);
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
const locales = input.messages.locales;
|
| 71 |
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
| 72 |
+
if (!locales) {
|
| 73 |
+
throwError('`messages.locales` is required when extracting messages.');
|
| 74 |
+
}
|
| 75 |
+
if (input.messages.sourceLocale) {
|
| 76 |
+
sourceLocale = input.messages.sourceLocale;
|
| 77 |
+
}
|
| 78 |
+
if (!sourceLocale) {
|
| 79 |
+
throwError('`messages.sourceLocale` is required when extracting messages.');
|
| 80 |
+
}
|
| 81 |
+
const srcPath = input.srcPath;
|
| 82 |
+
if (srcPath == null) {
|
| 83 |
+
throwError('`srcPath` is required when extracting messages.');
|
| 84 |
+
}
|
| 85 |
+
const pathIsArray = Array.isArray(input.messages.path);
|
| 86 |
+
const messagesPath = normalizeMessagesCatalogPaths(input.messages.path);
|
| 87 |
+
if (messagesPath.length === 0) {
|
| 88 |
+
throwError('`messages.path` must not be empty.');
|
| 89 |
+
}
|
| 90 |
+
if (extractPath == null) {
|
| 91 |
+
if (pathIsArray) {
|
| 92 |
+
throwError('When `messages.path` is an array, `extract.path` is required to select the writable catalog directory.');
|
| 93 |
+
}
|
| 94 |
+
extractPath = messagesPath[0];
|
| 95 |
+
}
|
| 96 |
+
return {
|
| 97 |
+
extract: {
|
| 98 |
+
locales,
|
| 99 |
+
path: extractPath,
|
| 100 |
+
sourceLocale,
|
| 101 |
+
srcPath
|
| 102 |
+
},
|
| 103 |
+
messages: {
|
| 104 |
+
format: input.messages.format,
|
| 105 |
+
path: messagesPath
|
| 106 |
+
}
|
| 107 |
+
};
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
/**
|
| 111 |
+
* Wrapper around `fs.watch` that provides a workaround
|
| 112 |
+
* for https://github.com/nodejs/node/issues/5039.
|
| 113 |
+
*/
|
| 114 |
+
function watchFile(filepath, callback) {
|
| 115 |
+
const directory = path__default.default.dirname(filepath);
|
| 116 |
+
const filename = path__default.default.basename(filepath);
|
| 117 |
+
return fs__default.default.watch(directory, {
|
| 118 |
+
persistent: false,
|
| 119 |
+
recursive: false
|
| 120 |
+
}, (event, changedFilename) => {
|
| 121 |
+
if (changedFilename === filename) {
|
| 122 |
+
callback();
|
| 123 |
+
}
|
| 124 |
+
});
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
const runOnce$1 = once('_NEXT_INTL_COMPILE_MESSAGES');
|
| 128 |
+
function createMessagesDeclaration(messagesPaths) {
|
| 129 |
+
// Instead of running _only_ in certain cases, it's
|
| 130 |
+
// safer to _avoid_ running for certain known cases.
|
| 131 |
+
// https://github.com/amannn/next-intl/issues/2006
|
| 132 |
+
const shouldBailOut = ['info', 'start'
|
| 133 |
+
|
| 134 |
+
// Note: These commands don't consult the
|
| 135 |
+
// Next.js config, so we can't detect them here.
|
| 136 |
+
// - telemetry (however, the `detached-flush` DOES - see `createNextIntlPlugin`)
|
| 137 |
+
// - lint
|
| 138 |
+
//
|
| 139 |
+
// What remains are:
|
| 140 |
+
// - dev
|
| 141 |
+
// - build
|
| 142 |
+
// - typegen
|
| 143 |
+
].some(arg => process.argv.includes(arg));
|
| 144 |
+
if (shouldBailOut) {
|
| 145 |
+
return;
|
| 146 |
+
}
|
| 147 |
+
runOnce$1(() => {
|
| 148 |
+
for (const messagesPath of messagesPaths) {
|
| 149 |
+
const fullPath = path__default.default.resolve(messagesPath);
|
| 150 |
+
if (!fs__default.default.existsSync(fullPath)) {
|
| 151 |
+
throwError(`\`createMessagesDeclaration\` points to a non-existent file: ${fullPath}`);
|
| 152 |
+
}
|
| 153 |
+
if (!fullPath.endsWith('.json')) {
|
| 154 |
+
throwError(`\`createMessagesDeclaration\` needs to point to a JSON file. Received: ${fullPath}`);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
// Keep this as a runtime check and don't replace
|
| 158 |
+
// this with a constant during the build process
|
| 159 |
+
const env = process.env['NODE_ENV'.trim()];
|
| 160 |
+
compileDeclaration(messagesPath);
|
| 161 |
+
if (env === 'development') {
|
| 162 |
+
startWatching(messagesPath);
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
});
|
| 166 |
+
}
|
| 167 |
+
function startWatching(messagesPath) {
|
| 168 |
+
const watcher = watchFile(messagesPath, () => {
|
| 169 |
+
compileDeclaration(messagesPath, true);
|
| 170 |
+
});
|
| 171 |
+
process.on('exit', () => {
|
| 172 |
+
watcher.close();
|
| 173 |
+
});
|
| 174 |
+
}
|
| 175 |
+
function compileDeclaration(messagesPath, async = false) {
|
| 176 |
+
const declarationPath = messagesPath.replace(/\.json$/, '.d.json.ts');
|
| 177 |
+
function createDeclaration(content) {
|
| 178 |
+
return `// This file is auto-generated by next-intl, do not edit directly.
|
| 179 |
+
// See: https://next-intl.dev/docs/workflows/typescript#messages-arguments
|
| 180 |
+
|
| 181 |
+
declare const messages: ${content.trim()};
|
| 182 |
+
export default messages;`;
|
| 183 |
+
}
|
| 184 |
+
if (async) {
|
| 185 |
+
return fs__default.default.promises.readFile(messagesPath, 'utf-8').then(content => fs__default.default.promises.writeFile(declarationPath, createDeclaration(content)));
|
| 186 |
+
}
|
| 187 |
+
const content = fs__default.default.readFileSync(messagesPath, 'utf-8');
|
| 188 |
+
fs__default.default.writeFileSync(declarationPath, createDeclaration(content));
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
const formats = {
|
| 192 |
+
json: {
|
| 193 |
+
codec: () => import('@eloqnt/format-json'),
|
| 194 |
+
extension: '.json'
|
| 195 |
+
},
|
| 196 |
+
po: {
|
| 197 |
+
codec: () => Promise.resolve().then(function () { return require('./BuiltInPoCodec-B6w-kmTx.cjs'); }),
|
| 198 |
+
extension: '.po'
|
| 199 |
+
}
|
| 200 |
+
};
|
| 201 |
+
function isBuiltInFormat(format) {
|
| 202 |
+
return typeof format === 'string' && format in formats;
|
| 203 |
+
}
|
| 204 |
+
function getFormatExtension(format) {
|
| 205 |
+
if (isBuiltInFormat(format)) {
|
| 206 |
+
return formats[format].extension;
|
| 207 |
+
} else {
|
| 208 |
+
return format.extension;
|
| 209 |
+
}
|
| 210 |
+
}
|
| 211 |
+
async function resolveCodec(format, projectRoot) {
|
| 212 |
+
if (isBuiltInFormat(format)) {
|
| 213 |
+
const factory = (await formats[format].codec()).default;
|
| 214 |
+
return factory();
|
| 215 |
+
} else {
|
| 216 |
+
// A codec that starts with `.` or a filesystem root is a file path;
|
| 217 |
+
// everything else reads as a package specifier, mirroring how imports
|
| 218 |
+
// distinguish the two. Packages resolve from the project's dependencies.
|
| 219 |
+
const isPath = format.codec.startsWith('.') || path__default.default.isAbsolute(format.codec);
|
| 220 |
+
let resolvedPath;
|
| 221 |
+
if (isPath) {
|
| 222 |
+
resolvedPath = path__default.default.resolve(projectRoot, format.codec);
|
| 223 |
+
} else {
|
| 224 |
+
try {
|
| 225 |
+
resolvedPath = module$1.createRequire(path__default.default.join(projectRoot, 'noop.js')).resolve(format.codec);
|
| 226 |
+
} catch (error) {
|
| 227 |
+
throwError(`Could not resolve the codec package "${format.codec}". Is it installed?\n${error}`);
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
let module;
|
| 231 |
+
try {
|
| 232 |
+
module = await import(url.pathToFileURL(resolvedPath).href);
|
| 233 |
+
} catch (error) {
|
| 234 |
+
throwError(`Could not load codec from "${resolvedPath}".\n${error}`);
|
| 235 |
+
}
|
| 236 |
+
const factory = module.default;
|
| 237 |
+
if (!factory || typeof factory !== 'function') {
|
| 238 |
+
throwError(`Codec at "${resolvedPath}" must have a default export returned from \`defineCodec\`.`);
|
| 239 |
+
}
|
| 240 |
+
return factory();
|
| 241 |
+
}
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
class SourceFileFilter {
|
| 245 |
+
static EXTENSIONS = ['ts', 'tsx', 'js', 'jsx'];
|
| 246 |
+
|
| 247 |
+
// Will not be entered, except if explicitly asked for
|
| 248 |
+
// TODO: At some point we should infer these from .gitignore
|
| 249 |
+
static IGNORED_DIRECTORIES = ['node_modules', '.next', '.git'];
|
| 250 |
+
static isSourceFile(filePath) {
|
| 251 |
+
const ext = path__default.default.extname(filePath);
|
| 252 |
+
return SourceFileFilter.EXTENSIONS.map(cur => '.' + cur).includes(ext);
|
| 253 |
+
}
|
| 254 |
+
static shouldEnterDirectory(dirPath, srcPaths) {
|
| 255 |
+
const dirName = path__default.default.basename(dirPath);
|
| 256 |
+
if (SourceFileFilter.IGNORED_DIRECTORIES.includes(dirName)) {
|
| 257 |
+
return SourceFileFilter.isIgnoredDirectoryExplicitlyIncluded(dirPath, srcPaths);
|
| 258 |
+
}
|
| 259 |
+
return true;
|
| 260 |
+
}
|
| 261 |
+
static isIgnoredDirectoryExplicitlyIncluded(ignoredDirPath, srcPaths) {
|
| 262 |
+
return srcPaths.some(srcPath => SourceFileFilter.isWithinPath(srcPath, ignoredDirPath));
|
| 263 |
+
}
|
| 264 |
+
static isWithinPath(targetPath, basePath) {
|
| 265 |
+
const relativePath = path__default.default.relative(basePath, targetPath);
|
| 266 |
+
return relativePath === '' || !relativePath.startsWith('..');
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
class SourceFileScanner {
|
| 271 |
+
static async walkSourceFiles(dir, srcPaths, acc = []) {
|
| 272 |
+
const entries = await fs__default$1.default.readdir(dir, {
|
| 273 |
+
withFileTypes: true
|
| 274 |
+
});
|
| 275 |
+
for (const entry of entries) {
|
| 276 |
+
const entryPath = path__default.default.join(dir, entry.name);
|
| 277 |
+
if (entry.isDirectory()) {
|
| 278 |
+
if (!SourceFileFilter.shouldEnterDirectory(entryPath, srcPaths)) {
|
| 279 |
+
continue;
|
| 280 |
+
}
|
| 281 |
+
await SourceFileScanner.walkSourceFiles(entryPath, srcPaths, acc);
|
| 282 |
+
} else {
|
| 283 |
+
if (SourceFileFilter.isSourceFile(entry.name)) {
|
| 284 |
+
acc.push(entryPath);
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
}
|
| 288 |
+
return acc;
|
| 289 |
+
}
|
| 290 |
+
static async getSourceFiles(srcPaths) {
|
| 291 |
+
const files = (await Promise.all(srcPaths.map(srcPath => SourceFileScanner.walkSourceFiles(srcPath, srcPaths)))).flat();
|
| 292 |
+
return new Set(files);
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
class SourceFileWatcher {
|
| 297 |
+
subscriptions = [];
|
| 298 |
+
constructor(roots, onChange) {
|
| 299 |
+
this.roots = roots;
|
| 300 |
+
this.onChange = onChange;
|
| 301 |
+
}
|
| 302 |
+
async start() {
|
| 303 |
+
if (this.subscriptions.length > 0) {
|
| 304 |
+
return;
|
| 305 |
+
}
|
| 306 |
+
const ignore = SourceFileFilter.IGNORED_DIRECTORIES.map(dir => `**/${dir}/**`);
|
| 307 |
+
for (const root of this.roots) {
|
| 308 |
+
const sub = await watcher.subscribe(root, async (err, events) => {
|
| 309 |
+
if (err) {
|
| 310 |
+
console.error(err);
|
| 311 |
+
return;
|
| 312 |
+
}
|
| 313 |
+
const filtered = await this.normalizeEvents(events);
|
| 314 |
+
if (filtered.length > 0) {
|
| 315 |
+
await this.onChange(filtered);
|
| 316 |
+
}
|
| 317 |
+
}, {
|
| 318 |
+
ignore
|
| 319 |
+
});
|
| 320 |
+
this.subscriptions.push(sub);
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
async normalizeEvents(events) {
|
| 324 |
+
const directoryCreatePaths = [];
|
| 325 |
+
const otherEvents = [];
|
| 326 |
+
|
| 327 |
+
// We need to expand directory creates because during rename operations,
|
| 328 |
+
// @parcel/watcher emits a directory create event but may not emit individual
|
| 329 |
+
// file events for the moved files
|
| 330 |
+
await Promise.all(events.map(async event => {
|
| 331 |
+
if (event.type === 'create') {
|
| 332 |
+
try {
|
| 333 |
+
const stats = await fs__default$1.default.stat(event.path);
|
| 334 |
+
if (stats.isDirectory()) {
|
| 335 |
+
directoryCreatePaths.push(event.path);
|
| 336 |
+
return;
|
| 337 |
+
}
|
| 338 |
+
} catch {
|
| 339 |
+
// Path doesn't exist or is inaccessible, treat as file
|
| 340 |
+
}
|
| 341 |
+
}
|
| 342 |
+
otherEvents.push(event);
|
| 343 |
+
}));
|
| 344 |
+
|
| 345 |
+
// Expand directory create events to find source files inside
|
| 346 |
+
let expandedCreateEvents = [];
|
| 347 |
+
if (directoryCreatePaths.length > 0) {
|
| 348 |
+
try {
|
| 349 |
+
const sourceFiles = await SourceFileScanner.getSourceFiles(directoryCreatePaths);
|
| 350 |
+
expandedCreateEvents = Array.from(sourceFiles).map(filePath => ({
|
| 351 |
+
type: 'create',
|
| 352 |
+
path: filePath
|
| 353 |
+
}));
|
| 354 |
+
} catch {
|
| 355 |
+
// Directories might have been deleted or are inaccessible
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
// Combine original events with expanded directory creates.
|
| 360 |
+
// Deduplicate by path to avoid processing the same file twice
|
| 361 |
+
// in case @parcel/watcher also emitted individual file events.
|
| 362 |
+
const allEvents = [...otherEvents, ...expandedCreateEvents];
|
| 363 |
+
const seenPaths = new Set();
|
| 364 |
+
const deduplicated = [];
|
| 365 |
+
for (const event of allEvents) {
|
| 366 |
+
const key = `${event.type}:${event.path}`;
|
| 367 |
+
if (!seenPaths.has(key)) {
|
| 368 |
+
seenPaths.add(key);
|
| 369 |
+
deduplicated.push(event);
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
return deduplicated.filter(event => {
|
| 373 |
+
// Keep all delete events (might be deleted directories that no longer exist)
|
| 374 |
+
if (event.type === 'delete') {
|
| 375 |
+
return true;
|
| 376 |
+
}
|
| 377 |
+
// Keep source files
|
| 378 |
+
return SourceFileFilter.isSourceFile(event.path);
|
| 379 |
+
});
|
| 380 |
+
}
|
| 381 |
+
async expandDirectoryDeleteEvents(events, prevKnownFiles) {
|
| 382 |
+
const expanded = [];
|
| 383 |
+
for (const event of events) {
|
| 384 |
+
if (event.type === 'delete' && !SourceFileFilter.isSourceFile(event.path)) {
|
| 385 |
+
const dirPath = path__default.default.resolve(event.path);
|
| 386 |
+
const filesInDirectory = [];
|
| 387 |
+
for (const filePath of prevKnownFiles) {
|
| 388 |
+
if (SourceFileFilter.isWithinPath(filePath, dirPath)) {
|
| 389 |
+
filesInDirectory.push(filePath);
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
// If we found files within this path, it was a directory
|
| 394 |
+
if (filesInDirectory.length > 0) {
|
| 395 |
+
for (const filePath of filesInDirectory) {
|
| 396 |
+
expanded.push({
|
| 397 |
+
type: 'delete',
|
| 398 |
+
path: filePath
|
| 399 |
+
});
|
| 400 |
+
}
|
| 401 |
+
} else {
|
| 402 |
+
// Not a directory or no files in it, pass through as-is
|
| 403 |
+
expanded.push(event);
|
| 404 |
+
}
|
| 405 |
+
} else {
|
| 406 |
+
// Pass through as-is
|
| 407 |
+
expanded.push(event);
|
| 408 |
+
}
|
| 409 |
+
}
|
| 410 |
+
return expanded;
|
| 411 |
+
}
|
| 412 |
+
async stop() {
|
| 413 |
+
await Promise.all(this.subscriptions.map(sub => sub.unsubscribe()));
|
| 414 |
+
this.subscriptions = [];
|
| 415 |
+
}
|
| 416 |
+
[Symbol.dispose]() {
|
| 417 |
+
void this.stop();
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
function normalizePathToPosix(filePath) {
|
| 422 |
+
// `path.relative` uses OS-specific separators. For stable `.po` references we
|
| 423 |
+
// always use POSIX separators, regardless of the OS that ran extraction.
|
| 424 |
+
return path__default.default.posix.normalize(filePath.split(path__default.default.win32.sep).join(path__default.default.posix.sep));
|
| 425 |
+
}
|
| 426 |
+
function hasLocalesToExtract(config) {
|
| 427 |
+
const {
|
| 428 |
+
locales
|
| 429 |
+
} = config.extract;
|
| 430 |
+
return locales === 'infer' || locales.length > 0;
|
| 431 |
+
}
|
| 432 |
+
function warnAboutMissingReferences(messages) {
|
| 433 |
+
const warnedIds = new Set();
|
| 434 |
+
for (const message of messages) {
|
| 435 |
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
| 436 |
+
if (message.references[0] == null && !warnedIds.has(message.id)) {
|
| 437 |
+
warnedIds.add(message.id);
|
| 438 |
+
warn(`Missing file reference for extracted message: ${message.id}`);
|
| 439 |
+
}
|
| 440 |
+
}
|
| 441 |
+
}
|
| 442 |
+
function localeCompare(a, b) {
|
| 443 |
+
return a.localeCompare(b, 'en');
|
| 444 |
+
}
|
| 445 |
+
function compareReferences(refA, refB) {
|
| 446 |
+
const pathCompare = localeCompare(refA.path, refB.path);
|
| 447 |
+
if (pathCompare !== 0) return pathCompare;
|
| 448 |
+
return (refA.line ?? 0) - (refB.line ?? 0) || (refA.column ?? 0) - (refB.column ?? 0);
|
| 449 |
+
}
|
| 450 |
+
function getDefaultProjectRoot() {
|
| 451 |
+
return process.cwd();
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
class CatalogLocales {
|
| 455 |
+
onChangeCallbacks = new Set();
|
| 456 |
+
constructor(params) {
|
| 457 |
+
this.messagesDir = params.messagesDir;
|
| 458 |
+
this.sourceLocale = params.sourceLocale;
|
| 459 |
+
this.extension = params.extension;
|
| 460 |
+
this.locales = params.locales;
|
| 461 |
+
}
|
| 462 |
+
async getTargetLocales() {
|
| 463 |
+
if (this.targetLocales) {
|
| 464 |
+
return this.targetLocales;
|
| 465 |
+
}
|
| 466 |
+
if (this.locales === 'infer') {
|
| 467 |
+
this.targetLocales = await this.readTargetLocales();
|
| 468 |
+
} else {
|
| 469 |
+
this.targetLocales = this.locales.filter(locale => locale !== this.sourceLocale);
|
| 470 |
+
}
|
| 471 |
+
return this.targetLocales;
|
| 472 |
+
}
|
| 473 |
+
async readTargetLocales() {
|
| 474 |
+
try {
|
| 475 |
+
const files = await fs__default$1.default.readdir(this.messagesDir);
|
| 476 |
+
return files.filter(file => file.endsWith(this.extension)).map(file => path__default.default.basename(file, this.extension)).filter(locale => locale !== this.sourceLocale);
|
| 477 |
+
} catch {
|
| 478 |
+
return [];
|
| 479 |
+
}
|
| 480 |
+
}
|
| 481 |
+
subscribeLocalesChange(callback) {
|
| 482 |
+
this.onChangeCallbacks.add(callback);
|
| 483 |
+
if (this.locales === 'infer' && !this.watcher) {
|
| 484 |
+
void this.startWatcher();
|
| 485 |
+
}
|
| 486 |
+
}
|
| 487 |
+
unsubscribeLocalesChange(callback) {
|
| 488 |
+
this.onChangeCallbacks.delete(callback);
|
| 489 |
+
if (this.onChangeCallbacks.size === 0) {
|
| 490 |
+
this.stopWatcher();
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
async startWatcher() {
|
| 494 |
+
if (this.watcher) {
|
| 495 |
+
return;
|
| 496 |
+
}
|
| 497 |
+
await fs__default$1.default.mkdir(this.messagesDir, {
|
| 498 |
+
recursive: true
|
| 499 |
+
});
|
| 500 |
+
this.watcher = fs__default.default.watch(this.messagesDir, {
|
| 501 |
+
persistent: false,
|
| 502 |
+
recursive: false
|
| 503 |
+
}, (event, filename) => {
|
| 504 |
+
const isCatalogFile = filename != null && filename.endsWith(this.extension) && !filename.includes(path__default.default.sep);
|
| 505 |
+
if (isCatalogFile) {
|
| 506 |
+
void this.onChange();
|
| 507 |
+
}
|
| 508 |
+
});
|
| 509 |
+
}
|
| 510 |
+
stopWatcher() {
|
| 511 |
+
if (this.watcher) {
|
| 512 |
+
this.watcher.close();
|
| 513 |
+
this.watcher = undefined;
|
| 514 |
+
}
|
| 515 |
+
}
|
| 516 |
+
async onChange() {
|
| 517 |
+
const oldLocales = new Set(this.targetLocales || []);
|
| 518 |
+
this.targetLocales = await this.readTargetLocales();
|
| 519 |
+
const newLocalesSet = new Set(this.targetLocales);
|
| 520 |
+
const added = this.targetLocales.filter(locale => !oldLocales.has(locale));
|
| 521 |
+
const removed = Array.from(oldLocales).filter(locale => !newLocalesSet.has(locale));
|
| 522 |
+
if (added.length > 0 || removed.length > 0) {
|
| 523 |
+
for (const callback of this.onChangeCallbacks) {
|
| 524 |
+
callback({
|
| 525 |
+
added,
|
| 526 |
+
removed
|
| 527 |
+
});
|
| 528 |
+
}
|
| 529 |
+
}
|
| 530 |
+
}
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
class CatalogPersister {
|
| 534 |
+
constructor(params) {
|
| 535 |
+
this.messagesPath = params.messagesPath;
|
| 536 |
+
this.codec = params.codec;
|
| 537 |
+
this.extension = params.extension;
|
| 538 |
+
this.sourceLocale = params.sourceLocale;
|
| 539 |
+
}
|
| 540 |
+
getFileName(locale) {
|
| 541 |
+
return locale + this.extension;
|
| 542 |
+
}
|
| 543 |
+
getFilePath(locale) {
|
| 544 |
+
return path__default.default.join(this.messagesPath, this.getFileName(locale));
|
| 545 |
+
}
|
| 546 |
+
async read(locale) {
|
| 547 |
+
const filePath = this.getFilePath(locale);
|
| 548 |
+
let content;
|
| 549 |
+
try {
|
| 550 |
+
content = await fs__default$1.default.readFile(filePath, 'utf8');
|
| 551 |
+
} catch (error) {
|
| 552 |
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
| 553 |
+
return [];
|
| 554 |
+
}
|
| 555 |
+
throw new Error(`Error while reading ${this.getFileName(locale)}:\n> ${error}`, {
|
| 556 |
+
cause: error
|
| 557 |
+
});
|
| 558 |
+
}
|
| 559 |
+
try {
|
| 560 |
+
const messages = this.codec.decode(content, {
|
| 561 |
+
locale,
|
| 562 |
+
sourceLocale: this.sourceLocale
|
| 563 |
+
});
|
| 564 |
+
assertNoArrayMessages(messages);
|
| 565 |
+
return messages;
|
| 566 |
+
} catch (error) {
|
| 567 |
+
throw new Error(`Error while decoding ${this.getFileName(locale)}:\n> ${error}`, {
|
| 568 |
+
cause: error
|
| 569 |
+
});
|
| 570 |
+
}
|
| 571 |
+
}
|
| 572 |
+
async write(messages, context) {
|
| 573 |
+
const filePath = this.getFilePath(context.locale);
|
| 574 |
+
warnAboutMissingReferences(messages);
|
| 575 |
+
const content = this.codec.encode(messages, context);
|
| 576 |
+
try {
|
| 577 |
+
const outputDir = path__default.default.dirname(filePath);
|
| 578 |
+
await fs__default$1.default.mkdir(outputDir, {
|
| 579 |
+
recursive: true
|
| 580 |
+
});
|
| 581 |
+
await fs__default$1.default.writeFile(filePath, content);
|
| 582 |
+
} catch (error) {
|
| 583 |
+
console.error(`❌ Failed to write catalog: ${error}`);
|
| 584 |
+
}
|
| 585 |
+
}
|
| 586 |
+
async getLastModified(locale) {
|
| 587 |
+
const filePath = this.getFilePath(locale);
|
| 588 |
+
try {
|
| 589 |
+
const stats = await fs__default$1.default.stat(filePath);
|
| 590 |
+
return stats.mtime;
|
| 591 |
+
} catch {
|
| 592 |
+
return undefined;
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
// The codecs read arrays fine (numeric path segments), but the runtime only
|
| 598 |
+
// supports strings — enforced here where the runtime's rules apply.
|
| 599 |
+
function assertNoArrayMessages(messages) {
|
| 600 |
+
for (const message of messages) {
|
| 601 |
+
const segments = message.segments;
|
| 602 |
+
if (!Array.isArray(segments)) continue;
|
| 603 |
+
const firstIndex = segments.findIndex(segment => typeof segment === 'number');
|
| 604 |
+
if (firstIndex === -1) continue;
|
| 605 |
+
const arrayPath = segments.slice(0, firstIndex).join('.');
|
| 606 |
+
throw new Error(`Message at \`${arrayPath}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);
|
| 607 |
+
}
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
/**
|
| 611 |
+
* De-duplicates excessive save invocations,
|
| 612 |
+
* while keeping a single one instant.
|
| 613 |
+
*/
|
| 614 |
+
class SaveScheduler {
|
| 615 |
+
isSaving = false;
|
| 616 |
+
pendingResolvers = [];
|
| 617 |
+
constructor(delayMs = 50) {
|
| 618 |
+
this.delayMs = delayMs;
|
| 619 |
+
}
|
| 620 |
+
async schedule(saveTask) {
|
| 621 |
+
return new Promise((resolve, reject) => {
|
| 622 |
+
this.pendingResolvers.push({
|
| 623 |
+
resolve,
|
| 624 |
+
reject
|
| 625 |
+
});
|
| 626 |
+
this.nextSaveTask = saveTask;
|
| 627 |
+
if (!this.isSaving && !this.saveTimeout) {
|
| 628 |
+
// Not currently saving and no scheduled save, save immediately
|
| 629 |
+
this.executeSave();
|
| 630 |
+
} else if (this.saveTimeout) {
|
| 631 |
+
// A save is already scheduled, reschedule to debounce
|
| 632 |
+
this.scheduleSave();
|
| 633 |
+
}
|
| 634 |
+
// If isSaving is true and no timeout is scheduled, the current save
|
| 635 |
+
// will check for pending resolvers when it completes and schedule
|
| 636 |
+
// another save if needed (see finally block in executeSave)
|
| 637 |
+
});
|
| 638 |
+
}
|
| 639 |
+
scheduleSave() {
|
| 640 |
+
if (this.saveTimeout) {
|
| 641 |
+
clearTimeout(this.saveTimeout);
|
| 642 |
+
}
|
| 643 |
+
this.saveTimeout = setTimeout(() => {
|
| 644 |
+
this.saveTimeout = undefined;
|
| 645 |
+
this.executeSave();
|
| 646 |
+
}, this.delayMs);
|
| 647 |
+
}
|
| 648 |
+
async executeSave() {
|
| 649 |
+
if (this.isSaving) {
|
| 650 |
+
return;
|
| 651 |
+
}
|
| 652 |
+
const saveTask = this.nextSaveTask;
|
| 653 |
+
if (!saveTask) {
|
| 654 |
+
return;
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
// Capture current pending resolvers for this save
|
| 658 |
+
const resolversForThisSave = this.pendingResolvers;
|
| 659 |
+
this.pendingResolvers = [];
|
| 660 |
+
this.nextSaveTask = undefined;
|
| 661 |
+
this.isSaving = true;
|
| 662 |
+
try {
|
| 663 |
+
const result = await saveTask();
|
| 664 |
+
|
| 665 |
+
// Resolve only the promises that were pending when this save started
|
| 666 |
+
resolversForThisSave.forEach(({
|
| 667 |
+
resolve
|
| 668 |
+
}) => resolve(result));
|
| 669 |
+
} catch (error) {
|
| 670 |
+
// Reject only the promises that were pending when this save started
|
| 671 |
+
resolversForThisSave.forEach(({
|
| 672 |
+
reject
|
| 673 |
+
}) => reject(error));
|
| 674 |
+
} finally {
|
| 675 |
+
this.isSaving = false;
|
| 676 |
+
|
| 677 |
+
// If new saves were requested during this save, schedule another
|
| 678 |
+
if (this.pendingResolvers.length > 0) {
|
| 679 |
+
this.scheduleSave();
|
| 680 |
+
}
|
| 681 |
+
}
|
| 682 |
+
}
|
| 683 |
+
[Symbol.dispose]() {
|
| 684 |
+
if (this.saveTimeout) {
|
| 685 |
+
clearTimeout(this.saveTimeout);
|
| 686 |
+
this.saveTimeout = undefined;
|
| 687 |
+
}
|
| 688 |
+
this.pendingResolvers = [];
|
| 689 |
+
this.nextSaveTask = undefined;
|
| 690 |
+
this.isSaving = false;
|
| 691 |
+
}
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
class CatalogManager {
|
| 695 |
+
/**
|
| 696 |
+
* Extraction-derived fields aggregated into `ExtractorMessage`.
|
| 697 |
+
* Source code is the source of truth for these fields, only ancillary
|
| 698 |
+
* codec fields may merge from disk (e.g. flags).
|
| 699 |
+
*/
|
| 700 |
+
static extractorOwnedAggregatorKeys = new Set(['description', 'id', 'message', 'references']);
|
| 701 |
+
/**
|
| 702 |
+
* Source of truth for statically extracted source messages,
|
| 703 |
+
* grouped by file and message ID.
|
| 704 |
+
*/
|
| 705 |
+
sourceMessagesByFile = new Map();
|
| 706 |
+
|
| 707 |
+
/**
|
| 708 |
+
* Reverse index for rebuilding aggregated messages without scanning all files.
|
| 709 |
+
* Contains the same `SourceExtractedMessage` arrays as `sourceMessagesByFile` and is
|
| 710 |
+
* kept in sync with it.
|
| 711 |
+
*/
|
| 712 |
+
sourceMessagesById = new Map();
|
| 713 |
+
|
| 714 |
+
/**
|
| 715 |
+
* Fast lookup for messages by ID, aggregated across all files. This combines
|
| 716 |
+
* metadata from `sourceMessagesById`, e.g. references and descriptions.
|
| 717 |
+
*/
|
| 718 |
+
messagesById = new Map();
|
| 719 |
+
|
| 720 |
+
/**
|
| 721 |
+
* This potentially also includes outdated ones that were initially available,
|
| 722 |
+
* but are not used anymore. This allows to restore them if they are used again.
|
| 723 |
+
**/
|
| 724 |
+
translationsByTargetLocale = new Map();
|
| 725 |
+
lastWriteByLocale = new Map();
|
| 726 |
+
|
| 727 |
+
// Cached instances
|
| 728 |
+
|
| 729 |
+
// Resolves when all catalogs are loaded
|
| 730 |
+
|
| 731 |
+
// Resolves when the initial project scan and processing is complete
|
| 732 |
+
|
| 733 |
+
constructor(config, opts) {
|
| 734 |
+
this.config = config;
|
| 735 |
+
this.saveScheduler = new SaveScheduler(opts.saveDebounceMs ?? 50);
|
| 736 |
+
this.projectRoot = opts.projectRoot ?? getDefaultProjectRoot();
|
| 737 |
+
this.isDevelopment = opts.isDevelopment ?? false;
|
| 738 |
+
this.extractor = opts.extractor;
|
| 739 |
+
if (this.isDevelopment) {
|
| 740 |
+
// We kick this off as early as possible, so we get notified about changes
|
| 741 |
+
// that happen during the initial project scan (while awaiting it to
|
| 742 |
+
// complete though)
|
| 743 |
+
this.sourceWatcher = new SourceFileWatcher(this.getSrcPaths(), this.handleFileEvents.bind(this));
|
| 744 |
+
void this.sourceWatcher.start();
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
async getCodec() {
|
| 748 |
+
if (!this.codec) {
|
| 749 |
+
this.codec = await resolveCodec(this.config.messages.format, this.projectRoot);
|
| 750 |
+
}
|
| 751 |
+
return this.codec;
|
| 752 |
+
}
|
| 753 |
+
async getPersister() {
|
| 754 |
+
if (this.persister) {
|
| 755 |
+
return this.persister;
|
| 756 |
+
} else {
|
| 757 |
+
this.persister = new CatalogPersister({
|
| 758 |
+
messagesPath: this.config.extract.path,
|
| 759 |
+
codec: await this.getCodec(),
|
| 760 |
+
extension: getFormatExtension(this.config.messages.format),
|
| 761 |
+
sourceLocale: this.config.extract.sourceLocale
|
| 762 |
+
});
|
| 763 |
+
return this.persister;
|
| 764 |
+
}
|
| 765 |
+
}
|
| 766 |
+
getCatalogLocales() {
|
| 767 |
+
if (this.catalogLocales) {
|
| 768 |
+
return this.catalogLocales;
|
| 769 |
+
} else {
|
| 770 |
+
const messagesDir = path__default.default.join(this.projectRoot, this.config.extract.path);
|
| 771 |
+
this.catalogLocales = new CatalogLocales({
|
| 772 |
+
messagesDir,
|
| 773 |
+
extension: getFormatExtension(this.config.messages.format),
|
| 774 |
+
locales: this.config.extract.locales,
|
| 775 |
+
sourceLocale: this.config.extract.sourceLocale
|
| 776 |
+
});
|
| 777 |
+
return this.catalogLocales;
|
| 778 |
+
}
|
| 779 |
+
}
|
| 780 |
+
async getTargetLocales() {
|
| 781 |
+
return this.getCatalogLocales().getTargetLocales();
|
| 782 |
+
}
|
| 783 |
+
getSrcPaths() {
|
| 784 |
+
return (Array.isArray(this.config.extract.srcPath) ? this.config.extract.srcPath : [this.config.extract.srcPath]).map(srcPath => path__default.default.join(this.projectRoot, srcPath));
|
| 785 |
+
}
|
| 786 |
+
async loadMessages() {
|
| 787 |
+
const sourceDiskMessages = await this.loadSourceMessages();
|
| 788 |
+
this.loadCatalogsPromise = this.loadTargetMessages();
|
| 789 |
+
await this.loadCatalogsPromise;
|
| 790 |
+
this.scanCompletePromise = (async () => {
|
| 791 |
+
const sourceFiles = Array.from(await SourceFileScanner.getSourceFiles(this.getSrcPaths()))
|
| 792 |
+
// Stable file order keeps catalog ties independent of processing timing
|
| 793 |
+
.toSorted(localeCompare);
|
| 794 |
+
const extractedFiles = await Promise.all(sourceFiles.map(async filePath => ({
|
| 795 |
+
filePath,
|
| 796 |
+
messages: await this.extractFile(filePath)
|
| 797 |
+
})));
|
| 798 |
+
for (const {
|
| 799 |
+
filePath,
|
| 800 |
+
messages
|
| 801 |
+
} of extractedFiles) {
|
| 802 |
+
if (messages) {
|
| 803 |
+
this.applyFileMessages(filePath, messages);
|
| 804 |
+
}
|
| 805 |
+
}
|
| 806 |
+
this.mergeSourceDiskMetadata(sourceDiskMessages);
|
| 807 |
+
})();
|
| 808 |
+
await this.scanCompletePromise;
|
| 809 |
+
if (this.isDevelopment) {
|
| 810 |
+
const catalogLocales = this.getCatalogLocales();
|
| 811 |
+
catalogLocales.subscribeLocalesChange(this.onLocalesChange);
|
| 812 |
+
}
|
| 813 |
+
}
|
| 814 |
+
async loadSourceMessages() {
|
| 815 |
+
// Load source catalog to hydrate metadata (e.g. flags) later without
|
| 816 |
+
// treating catalog entries as source of truth.
|
| 817 |
+
const diskMessages = await this.loadLocaleMessages(this.config.extract.sourceLocale);
|
| 818 |
+
const byId = new Map();
|
| 819 |
+
for (const diskMessage of diskMessages) {
|
| 820 |
+
byId.set(diskMessage.id, diskMessage);
|
| 821 |
+
}
|
| 822 |
+
return byId;
|
| 823 |
+
}
|
| 824 |
+
async loadLocaleMessages(locale) {
|
| 825 |
+
const persister = await this.getPersister();
|
| 826 |
+
const messages = await persister.read(locale);
|
| 827 |
+
const fileTime = await persister.getLastModified(locale);
|
| 828 |
+
this.lastWriteByLocale.set(locale, fileTime);
|
| 829 |
+
return messages;
|
| 830 |
+
}
|
| 831 |
+
async loadTargetMessages() {
|
| 832 |
+
const targetLocales = await this.getTargetLocales();
|
| 833 |
+
await Promise.all(targetLocales.map(locale => this.reloadLocaleCatalog(locale)));
|
| 834 |
+
}
|
| 835 |
+
async reloadLocaleCatalog(locale) {
|
| 836 |
+
const diskMessages = await this.loadLocaleMessages(locale);
|
| 837 |
+
if (locale === this.config.extract.sourceLocale) {
|
| 838 |
+
// For source: Merge additional properties like flags
|
| 839 |
+
for (const diskMessage of diskMessages) {
|
| 840 |
+
const prev = this.messagesById.get(diskMessage.id);
|
| 841 |
+
if (prev) {
|
| 842 |
+
for (const key of Object.keys(diskMessage)) {
|
| 843 |
+
if (!CatalogManager.extractorOwnedAggregatorKeys.has(key)) {
|
| 844 |
+
// For unknown properties (like flags), disk wins
|
| 845 |
+
prev[key] = diskMessage[key];
|
| 846 |
+
}
|
| 847 |
+
}
|
| 848 |
+
}
|
| 849 |
+
}
|
| 850 |
+
} else {
|
| 851 |
+
// For target: disk wins completely, BUT preserve existing translations
|
| 852 |
+
// if we read empty (likely a write in progress by an external tool
|
| 853 |
+
// that causes the file to temporarily be empty)
|
| 854 |
+
const existingTranslations = this.translationsByTargetLocale.get(locale);
|
| 855 |
+
const hasExistingTranslations = existingTranslations && existingTranslations.size > 0;
|
| 856 |
+
if (diskMessages.length > 0) {
|
| 857 |
+
// We got content from disk, replace with it
|
| 858 |
+
const translations = new Map();
|
| 859 |
+
for (const message of diskMessages) {
|
| 860 |
+
translations.set(message.id, message);
|
| 861 |
+
}
|
| 862 |
+
this.translationsByTargetLocale.set(locale, translations);
|
| 863 |
+
} else if (hasExistingTranslations) ; else {
|
| 864 |
+
// We read empty and have no existing translations
|
| 865 |
+
const translations = new Map();
|
| 866 |
+
this.translationsByTargetLocale.set(locale, translations);
|
| 867 |
+
}
|
| 868 |
+
}
|
| 869 |
+
}
|
| 870 |
+
mergeSourceDiskMetadata(diskMessages) {
|
| 871 |
+
for (const [id, diskMessage] of diskMessages) {
|
| 872 |
+
const existing = this.messagesById.get(id);
|
| 873 |
+
if (!existing) continue;
|
| 874 |
+
|
| 875 |
+
// Fill unknown metadata from disk without replacing extraction-owned fields.
|
| 876 |
+
for (const key of Object.keys(diskMessage)) {
|
| 877 |
+
if (!CatalogManager.extractorOwnedAggregatorKeys.has(key) && existing[key] == null) {
|
| 878 |
+
existing[key] = diskMessage[key];
|
| 879 |
+
}
|
| 880 |
+
}
|
| 881 |
+
}
|
| 882 |
+
}
|
| 883 |
+
async processFile(absoluteFilePath) {
|
| 884 |
+
const messages = await this.extractFile(absoluteFilePath);
|
| 885 |
+
|
| 886 |
+
// `undefined` only when `extractFile()` throws. An empty array is success
|
| 887 |
+
// and must still run `applyFileMessages` to clear stale ids for this file.
|
| 888 |
+
if (!messages) return false;
|
| 889 |
+
return this.applyFileMessages(absoluteFilePath, messages);
|
| 890 |
+
}
|
| 891 |
+
async extractFile(absoluteFilePath) {
|
| 892 |
+
let messages = [];
|
| 893 |
+
try {
|
| 894 |
+
const content = await fs__default$1.default.readFile(absoluteFilePath, 'utf8');
|
| 895 |
+
let extraction;
|
| 896 |
+
try {
|
| 897 |
+
extraction = await this.extractor.extract(absoluteFilePath, content);
|
| 898 |
+
} catch {
|
| 899 |
+
return undefined;
|
| 900 |
+
}
|
| 901 |
+
messages = extraction.messages;
|
| 902 |
+
} catch (err) {
|
| 903 |
+
if (err.code !== 'ENOENT') {
|
| 904 |
+
throw err;
|
| 905 |
+
}
|
| 906 |
+
// ENOENT -> treat as no messages
|
| 907 |
+
}
|
| 908 |
+
return messages;
|
| 909 |
+
}
|
| 910 |
+
applyFileMessages(absoluteFilePath, messages) {
|
| 911 |
+
const prevFileMessages = this.sourceMessagesByFile.get(absoluteFilePath);
|
| 912 |
+
const nextFileMessages = this.groupSourceMessagesById(messages);
|
| 913 |
+
const affectedIds = new Set([...(prevFileMessages?.keys() ?? []), ...nextFileMessages.keys()]);
|
| 914 |
+
if (nextFileMessages.size > 0) {
|
| 915 |
+
this.sourceMessagesByFile.set(absoluteFilePath, nextFileMessages);
|
| 916 |
+
} else {
|
| 917 |
+
this.sourceMessagesByFile.delete(absoluteFilePath);
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
+
// Clear this file's contribution from the reverse index, then re-insert
|
| 921 |
+
// fresh rows and rebuild aggregates (messagesById) per touched id.
|
| 922 |
+
for (const id of affectedIds) {
|
| 923 |
+
const sourceMessagesForId = this.sourceMessagesById.get(id);
|
| 924 |
+
if (sourceMessagesForId) {
|
| 925 |
+
sourceMessagesForId.delete(absoluteFilePath);
|
| 926 |
+
// No files left for this id: drop the reverse-index entry.
|
| 927 |
+
if (sourceMessagesForId.size === 0) {
|
| 928 |
+
this.sourceMessagesById.delete(id);
|
| 929 |
+
}
|
| 930 |
+
}
|
| 931 |
+
const nextSourceMessagesForId = nextFileMessages.get(id);
|
| 932 |
+
if (nextSourceMessagesForId) {
|
| 933 |
+
let sourceMessagesByFile = this.sourceMessagesById.get(id);
|
| 934 |
+
if (!sourceMessagesByFile) {
|
| 935 |
+
sourceMessagesByFile = new Map();
|
| 936 |
+
this.sourceMessagesById.set(id, sourceMessagesByFile);
|
| 937 |
+
}
|
| 938 |
+
sourceMessagesByFile.set(absoluteFilePath, nextSourceMessagesForId);
|
| 939 |
+
}
|
| 940 |
+
this.rebuildMessageById(id);
|
| 941 |
+
}
|
| 942 |
+
const changed = this.haveMessagesChangedForFile(prevFileMessages, nextFileMessages);
|
| 943 |
+
return changed;
|
| 944 |
+
}
|
| 945 |
+
groupSourceMessagesById(messages) {
|
| 946 |
+
const result = new Map();
|
| 947 |
+
for (const message of messages) {
|
| 948 |
+
const messagesById = result.get(message.id);
|
| 949 |
+
if (messagesById) {
|
| 950 |
+
messagesById.push(message);
|
| 951 |
+
} else {
|
| 952 |
+
result.set(message.id, [message]);
|
| 953 |
+
}
|
| 954 |
+
}
|
| 955 |
+
return result;
|
| 956 |
+
}
|
| 957 |
+
rebuildMessageById(id) {
|
| 958 |
+
const sourceMessages = Array.from(this.sourceMessagesById.get(id)?.values() ?? []).flat();
|
| 959 |
+
if (sourceMessages.length === 0) {
|
| 960 |
+
this.messagesById.delete(id);
|
| 961 |
+
return;
|
| 962 |
+
}
|
| 963 |
+
const previousMessage = this.messagesById.get(id);
|
| 964 |
+
const aggregate = {
|
| 965 |
+
description: this.mergeDescriptions(sourceMessages),
|
| 966 |
+
id,
|
| 967 |
+
message: sourceMessages[0].message,
|
| 968 |
+
references: sourceMessages.map(message => message.reference).sort(compareReferences)
|
| 969 |
+
};
|
| 970 |
+
if (previousMessage) {
|
| 971 |
+
for (const key of Object.keys(previousMessage)) {
|
| 972 |
+
// Preserve extra fields (e.g. from disk/codec) across rebuilds; the
|
| 973 |
+
// four core fields above are always recomputed from source messages.
|
| 974 |
+
if (!CatalogManager.extractorOwnedAggregatorKeys.has(key) && aggregate[key] == null) {
|
| 975 |
+
aggregate[key] = previousMessage[key];
|
| 976 |
+
}
|
| 977 |
+
}
|
| 978 |
+
}
|
| 979 |
+
this.messagesById.set(id, aggregate);
|
| 980 |
+
}
|
| 981 |
+
mergeDescriptions(messages) {
|
| 982 |
+
const sortedByReference = messages.toSorted((a, b) => compareReferences(a.reference, b.reference));
|
| 983 |
+
const merged = [];
|
| 984 |
+
for (const message of sortedByReference) {
|
| 985 |
+
const {
|
| 986 |
+
description
|
| 987 |
+
} = message;
|
| 988 |
+
if (description != null && !merged.includes(description)) {
|
| 989 |
+
merged.push(description);
|
| 990 |
+
}
|
| 991 |
+
}
|
| 992 |
+
return merged;
|
| 993 |
+
}
|
| 994 |
+
haveMessagesChangedForFile(beforeMessages, afterMessages) {
|
| 995 |
+
// If one exists and the other doesn't, there's a change
|
| 996 |
+
if (!beforeMessages) {
|
| 997 |
+
return afterMessages.size > 0;
|
| 998 |
+
}
|
| 999 |
+
|
| 1000 |
+
// Different sizes means changes
|
| 1001 |
+
if (beforeMessages.size !== afterMessages.size) {
|
| 1002 |
+
return true;
|
| 1003 |
+
}
|
| 1004 |
+
|
| 1005 |
+
// Check differences in beforeMessages vs afterMessages
|
| 1006 |
+
for (const [id, prevSourceMessages] of beforeMessages) {
|
| 1007 |
+
const nextSourceMessages = afterMessages.get(id);
|
| 1008 |
+
if (!nextSourceMessages) {
|
| 1009 |
+
return true;
|
| 1010 |
+
}
|
| 1011 |
+
if (!this.areSourceMessageArraysEqual(prevSourceMessages, nextSourceMessages)) {
|
| 1012 |
+
return true; // Early exit on first difference
|
| 1013 |
+
}
|
| 1014 |
+
}
|
| 1015 |
+
return false;
|
| 1016 |
+
}
|
| 1017 |
+
areSourceMessageArraysEqual(messages1, messages2) {
|
| 1018 |
+
return messages1.length === messages2.length && messages1.every((message, index) => this.areSourceMessagesEqual(message, messages2[index]));
|
| 1019 |
+
}
|
| 1020 |
+
areSourceMessagesEqual(msg1, msg2) {
|
| 1021 |
+
return msg1.id === msg2.id && msg1.message === msg2.message && msg1.description === msg2.description && msg1.reference.path === msg2.reference.path && msg1.reference.line === msg2.reference.line && msg1.reference.column === msg2.reference.column;
|
| 1022 |
+
}
|
| 1023 |
+
async save() {
|
| 1024 |
+
return this.saveScheduler.schedule(() => this.saveImpl());
|
| 1025 |
+
}
|
| 1026 |
+
async saveImpl() {
|
| 1027 |
+
await this.saveLocale(this.config.extract.sourceLocale);
|
| 1028 |
+
const targetLocales = await this.getTargetLocales();
|
| 1029 |
+
await Promise.all(targetLocales.map(locale => this.saveLocale(locale)));
|
| 1030 |
+
}
|
| 1031 |
+
async saveLocale(locale) {
|
| 1032 |
+
await this.loadCatalogsPromise;
|
| 1033 |
+
const messages = Array.from(this.messagesById.values());
|
| 1034 |
+
const persister = await this.getPersister();
|
| 1035 |
+
const isSourceLocale = locale === this.config.extract.sourceLocale;
|
| 1036 |
+
|
| 1037 |
+
// Check if file was modified externally (poll-at-save is cheaper than
|
| 1038 |
+
// watchers here since stat() is fast and avoids continuous overhead)
|
| 1039 |
+
const lastWriteTime = this.lastWriteByLocale.get(locale);
|
| 1040 |
+
const currentFileTime = await persister.getLastModified(locale);
|
| 1041 |
+
if (currentFileTime && lastWriteTime && currentFileTime > lastWriteTime) {
|
| 1042 |
+
await this.reloadLocaleCatalog(locale);
|
| 1043 |
+
}
|
| 1044 |
+
const localeMessages = isSourceLocale ? this.messagesById : this.translationsByTargetLocale.get(locale);
|
| 1045 |
+
const messagesToPersist = messages.map(message => {
|
| 1046 |
+
const localeMessage = localeMessages?.get(message.id);
|
| 1047 |
+
return {
|
| 1048 |
+
...localeMessage,
|
| 1049 |
+
id: message.id,
|
| 1050 |
+
description: message.description,
|
| 1051 |
+
references: message.references,
|
| 1052 |
+
message: isSourceLocale ? message.message : localeMessage?.message ?? ''
|
| 1053 |
+
};
|
| 1054 |
+
});
|
| 1055 |
+
await persister.write(messagesToPersist, {
|
| 1056 |
+
locale,
|
| 1057 |
+
sourceMessagesById: this.messagesById
|
| 1058 |
+
});
|
| 1059 |
+
|
| 1060 |
+
// Update timestamps
|
| 1061 |
+
const newTime = await persister.getLastModified(locale);
|
| 1062 |
+
this.lastWriteByLocale.set(locale, newTime);
|
| 1063 |
+
}
|
| 1064 |
+
onLocalesChange = async params => {
|
| 1065 |
+
// Chain to existing promise
|
| 1066 |
+
this.loadCatalogsPromise = Promise.all([this.loadCatalogsPromise, ...params.added.map(locale => this.reloadLocaleCatalog(locale))]);
|
| 1067 |
+
for (const locale of params.added) {
|
| 1068 |
+
await this.saveLocale(locale);
|
| 1069 |
+
}
|
| 1070 |
+
for (const locale of params.removed) {
|
| 1071 |
+
this.translationsByTargetLocale.delete(locale);
|
| 1072 |
+
this.lastWriteByLocale.delete(locale);
|
| 1073 |
+
}
|
| 1074 |
+
};
|
| 1075 |
+
async handleFileEvents(events) {
|
| 1076 |
+
if (this.loadCatalogsPromise) {
|
| 1077 |
+
await this.loadCatalogsPromise;
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
// Wait for initial scan to complete to avoid race conditions
|
| 1081 |
+
if (this.scanCompletePromise) {
|
| 1082 |
+
await this.scanCompletePromise;
|
| 1083 |
+
}
|
| 1084 |
+
let changed = false;
|
| 1085 |
+
const expandedEvents = await this.sourceWatcher.expandDirectoryDeleteEvents(events, Array.from(this.sourceMessagesByFile.keys()));
|
| 1086 |
+
// Stable file order keeps catalog ties independent of event timing.
|
| 1087 |
+
for (const event of expandedEvents.toSorted((a, b) => localeCompare(a.path, b.path))) {
|
| 1088 |
+
const hasChanged = await this.processFile(event.path);
|
| 1089 |
+
changed ||= hasChanged;
|
| 1090 |
+
}
|
| 1091 |
+
if (changed) {
|
| 1092 |
+
await this.save();
|
| 1093 |
+
}
|
| 1094 |
+
}
|
| 1095 |
+
[Symbol.dispose]() {
|
| 1096 |
+
this.sourceWatcher?.stop();
|
| 1097 |
+
this.sourceWatcher = undefined;
|
| 1098 |
+
this.saveScheduler[Symbol.dispose]();
|
| 1099 |
+
if (this.catalogLocales && this.isDevelopment) {
|
| 1100 |
+
this.catalogLocales.unsubscribeLocalesChange(this.onLocalesChange);
|
| 1101 |
+
}
|
| 1102 |
+
}
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
class LRUCache {
|
| 1106 |
+
constructor(maxSize) {
|
| 1107 |
+
this.maxSize = maxSize;
|
| 1108 |
+
this.cache = new Map();
|
| 1109 |
+
}
|
| 1110 |
+
set(key, value) {
|
| 1111 |
+
const isNewKey = !this.cache.has(key);
|
| 1112 |
+
if (isNewKey && this.cache.size >= this.maxSize) {
|
| 1113 |
+
const lruKey = this.cache.keys().next().value;
|
| 1114 |
+
if (lruKey !== undefined) {
|
| 1115 |
+
this.cache.delete(lruKey);
|
| 1116 |
+
}
|
| 1117 |
+
}
|
| 1118 |
+
this.cache.set(key, {
|
| 1119 |
+
key,
|
| 1120 |
+
value
|
| 1121 |
+
});
|
| 1122 |
+
}
|
| 1123 |
+
get(key) {
|
| 1124 |
+
const item = this.cache.get(key);
|
| 1125 |
+
if (item) {
|
| 1126 |
+
this.cache.delete(key);
|
| 1127 |
+
this.cache.set(key, item);
|
| 1128 |
+
return item.value;
|
| 1129 |
+
}
|
| 1130 |
+
return undefined;
|
| 1131 |
+
}
|
| 1132 |
+
}
|
| 1133 |
+
|
| 1134 |
+
const require$2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin.cjs', document.baseURI).href)));
|
| 1135 |
+
class MessageExtractor {
|
| 1136 |
+
compileCache = new LRUCache(750);
|
| 1137 |
+
constructor(opts) {
|
| 1138 |
+
this.isDevelopment = opts.isDevelopment ?? false;
|
| 1139 |
+
this.projectRoot = opts.projectRoot ?? getDefaultProjectRoot();
|
| 1140 |
+
this.sourceMap = opts.sourceMap ?? false;
|
| 1141 |
+
}
|
| 1142 |
+
async extract(absoluteFilePath, source) {
|
| 1143 |
+
const cacheKey = [source, absoluteFilePath].join('!');
|
| 1144 |
+
const cached = this.compileCache.get(cacheKey);
|
| 1145 |
+
if (cached) return cached;
|
| 1146 |
+
|
| 1147 |
+
// Shortcut parsing if hook is not used. The Turbopack integration already
|
| 1148 |
+
// pre-filters this, but for webpack this feature doesn't exist, so we need
|
| 1149 |
+
// to do it here.
|
| 1150 |
+
if (!source.includes('useExtracted') && !source.includes('getExtracted')) {
|
| 1151 |
+
return {
|
| 1152 |
+
messages: [],
|
| 1153 |
+
code: source
|
| 1154 |
+
};
|
| 1155 |
+
}
|
| 1156 |
+
const filePath = normalizePathToPosix(path__default.default.relative(this.projectRoot, absoluteFilePath));
|
| 1157 |
+
const result = await core.transform(source, {
|
| 1158 |
+
jsc: {
|
| 1159 |
+
target: 'esnext',
|
| 1160 |
+
parser: {
|
| 1161 |
+
syntax: 'typescript',
|
| 1162 |
+
tsx: true,
|
| 1163 |
+
decorators: true
|
| 1164 |
+
},
|
| 1165 |
+
experimental: {
|
| 1166 |
+
cacheRoot: 'node_modules/.cache/swc',
|
| 1167 |
+
disableBuiltinTransformsForInternalTesting: true,
|
| 1168 |
+
disableAllLints: true,
|
| 1169 |
+
plugins: [[require$2.resolve('next-intl-swc-plugin-extractor'), {
|
| 1170 |
+
isDevelopment: this.isDevelopment,
|
| 1171 |
+
filePath
|
| 1172 |
+
}]]
|
| 1173 |
+
}
|
| 1174 |
+
},
|
| 1175 |
+
sourceMaps: this.sourceMap,
|
| 1176 |
+
// Is converted into a relative path by Next.js when
|
| 1177 |
+
// the final source map is generated
|
| 1178 |
+
sourceFileName: absoluteFilePath,
|
| 1179 |
+
filename: filePath
|
| 1180 |
+
});
|
| 1181 |
+
|
| 1182 |
+
// TODO: Improve the typing of @swc/core
|
| 1183 |
+
const output = result.output;
|
| 1184 |
+
// The plugin emits a tagged union of extracted messages and
|
| 1185 |
+
// `useTranslations` usages; this extractor only consumes the former.
|
| 1186 |
+
const messages = JSON.parse(JSON.parse(output).results).filter(item => item.type === 'extracted').map(item => ({
|
| 1187 |
+
id: item.id,
|
| 1188 |
+
message: item.message,
|
| 1189 |
+
description: item.description,
|
| 1190 |
+
reference: item.reference
|
| 1191 |
+
}));
|
| 1192 |
+
const extractionResult = {
|
| 1193 |
+
code: result.code,
|
| 1194 |
+
map: result.map,
|
| 1195 |
+
messages
|
| 1196 |
+
};
|
| 1197 |
+
this.compileCache.set(cacheKey, extractionResult);
|
| 1198 |
+
return extractionResult;
|
| 1199 |
+
}
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
class ExtractionCompiler {
|
| 1203 |
+
constructor(config, opts = {}) {
|
| 1204 |
+
const extractor = opts.extractor ?? new MessageExtractor(opts);
|
| 1205 |
+
this.manager = new CatalogManager(config, {
|
| 1206 |
+
...opts,
|
| 1207 |
+
extractor
|
| 1208 |
+
});
|
| 1209 |
+
this[Symbol.dispose] = this[Symbol.dispose].bind(this);
|
| 1210 |
+
this.installExitHandlers();
|
| 1211 |
+
}
|
| 1212 |
+
async extractAll() {
|
| 1213 |
+
// We can't rely on all files being compiled (e.g. due to persistent
|
| 1214 |
+
// caching), so loading the messages initially is necessary.
|
| 1215 |
+
await this.manager.loadMessages();
|
| 1216 |
+
await this.manager.save();
|
| 1217 |
+
}
|
| 1218 |
+
[Symbol.dispose]() {
|
| 1219 |
+
this.uninstallExitHandlers();
|
| 1220 |
+
this.manager[Symbol.dispose]();
|
| 1221 |
+
}
|
| 1222 |
+
installExitHandlers() {
|
| 1223 |
+
const cleanup = this[Symbol.dispose];
|
| 1224 |
+
process.on('exit', cleanup);
|
| 1225 |
+
process.on('SIGINT', cleanup);
|
| 1226 |
+
process.on('SIGTERM', cleanup);
|
| 1227 |
+
}
|
| 1228 |
+
uninstallExitHandlers() {
|
| 1229 |
+
const cleanup = this[Symbol.dispose];
|
| 1230 |
+
process.off('exit', cleanup);
|
| 1231 |
+
process.off('SIGINT', cleanup);
|
| 1232 |
+
process.off('SIGTERM', cleanup);
|
| 1233 |
+
}
|
| 1234 |
+
}
|
| 1235 |
+
|
| 1236 |
+
// Avoid rollup's `replace` plugin to compile this away
|
| 1237 |
+
const nodeEnvKey = 'NODE_ENV'.trim();
|
| 1238 |
+
|
| 1239 |
+
// We avoid reading `argv.includes('dev')` related to
|
| 1240 |
+
// https://github.com/amannn/next-intl/issues/2006
|
| 1241 |
+
const isDevelopment = process.env[nodeEnvKey] === 'development';
|
| 1242 |
+
const isNextBuild = process.argv.includes('build');
|
| 1243 |
+
const isDevelopmentOrNextBuild = isDevelopment || isNextBuild;
|
| 1244 |
+
|
| 1245 |
+
// Single compiler instance, initialized once per process
|
| 1246 |
+
let compiler;
|
| 1247 |
+
const runOnce = once('_NEXT_INTL_EXTRACT');
|
| 1248 |
+
function initExtractionCompiler(extractorConfig) {
|
| 1249 |
+
if (!extractorConfig || !hasLocalesToExtract(extractorConfig)) {
|
| 1250 |
+
return;
|
| 1251 |
+
}
|
| 1252 |
+
|
| 1253 |
+
// Avoid running for:
|
| 1254 |
+
// - info
|
| 1255 |
+
// - start
|
| 1256 |
+
// - typegen
|
| 1257 |
+
//
|
| 1258 |
+
// Doesn't consult Next.js config anyway:
|
| 1259 |
+
// - lint
|
| 1260 |
+
// - telemetry (however, the `detached-flush` DOES - see `createNextIntlPlugin`)
|
| 1261 |
+
//
|
| 1262 |
+
// What remains are:
|
| 1263 |
+
// - dev (NODE_ENV=development)
|
| 1264 |
+
// - build (NODE_ENV=production)
|
| 1265 |
+
const shouldRun = isDevelopment || isNextBuild;
|
| 1266 |
+
if (!shouldRun) return;
|
| 1267 |
+
runOnce(() => {
|
| 1268 |
+
compiler = new ExtractionCompiler(extractorConfig, {
|
| 1269 |
+
isDevelopment,
|
| 1270 |
+
projectRoot: process.cwd()
|
| 1271 |
+
});
|
| 1272 |
+
|
| 1273 |
+
// Fire-and-forget: Start extraction, don't block config return.
|
| 1274 |
+
// In dev mode, this also starts the file watcher.
|
| 1275 |
+
// In prod, ideally we would wait until the extraction is complete,
|
| 1276 |
+
// but we can't `await` anywhere (at least for Turbopack).
|
| 1277 |
+
// The result is ok though, as if we encounter untranslated messages,
|
| 1278 |
+
// we'll simply add empty messages to the catalog. So for actually
|
| 1279 |
+
// running the app, there is no difference.
|
| 1280 |
+
compiler.extractAll();
|
| 1281 |
+
function cleanup() {
|
| 1282 |
+
if (compiler) {
|
| 1283 |
+
compiler[Symbol.dispose]();
|
| 1284 |
+
compiler = undefined;
|
| 1285 |
+
}
|
| 1286 |
+
}
|
| 1287 |
+
process.on('exit', cleanup);
|
| 1288 |
+
process.on('SIGINT', cleanup);
|
| 1289 |
+
process.on('SIGTERM', cleanup);
|
| 1290 |
+
});
|
| 1291 |
+
}
|
| 1292 |
+
|
| 1293 |
+
function getCurrentVersion() {
|
| 1294 |
+
try {
|
| 1295 |
+
const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin.cjs', document.baseURI).href)));
|
| 1296 |
+
const pkg = require$1('next/package.json');
|
| 1297 |
+
return pkg.version;
|
| 1298 |
+
} catch (error) {
|
| 1299 |
+
throw new Error('Failed to get current Next.js version. This can happen if next-intl/plugin is imported into your app code outside of your next.config.js.', {
|
| 1300 |
+
cause: error
|
| 1301 |
+
});
|
| 1302 |
+
}
|
| 1303 |
+
}
|
| 1304 |
+
function compareVersions(version1, version2) {
|
| 1305 |
+
const v1Parts = version1.split('.').map(Number);
|
| 1306 |
+
const v2Parts = version2.split('.').map(Number);
|
| 1307 |
+
for (let i = 0; i < 3; i++) {
|
| 1308 |
+
const v1 = v1Parts[i] || 0;
|
| 1309 |
+
const v2 = v2Parts[i] || 0;
|
| 1310 |
+
if (v1 > v2) return 1;
|
| 1311 |
+
if (v1 < v2) return -1;
|
| 1312 |
+
}
|
| 1313 |
+
return 0;
|
| 1314 |
+
}
|
| 1315 |
+
function hasStableTurboConfig() {
|
| 1316 |
+
return compareVersions(getCurrentVersion(), '15.3.0') >= 0;
|
| 1317 |
+
}
|
| 1318 |
+
function isNextJs16OrHigher() {
|
| 1319 |
+
return compareVersions(getCurrentVersion(), '16.0.0') >= 0;
|
| 1320 |
+
}
|
| 1321 |
+
|
| 1322 |
+
const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin.cjs', document.baseURI).href)));
|
| 1323 |
+
function withExtensions(localPath) {
|
| 1324 |
+
return [`${localPath}.ts`, `${localPath}.tsx`, `${localPath}.js`, `${localPath}.jsx`];
|
| 1325 |
+
}
|
| 1326 |
+
function normalizeTurbopackAliasPath(pathname) {
|
| 1327 |
+
// Turbopack alias targets should use forward slashes; Windows backslashes can
|
| 1328 |
+
// break resolution in dev (see `next-intl/config` alias path style).
|
| 1329 |
+
return pathname.replace(/\\/g, '/');
|
| 1330 |
+
}
|
| 1331 |
+
|
| 1332 |
+
/**
|
| 1333 |
+
* Turbopack validates loader options by comparing them to their JSON round-trip
|
| 1334 |
+
* (`isDeepStrictEqual(options, JSON.parse(JSON.stringify(options)))`). Since
|
| 1335 |
+
* `JSON.stringify` drops keys with an `undefined` value, such a key would always
|
| 1336 |
+
* fail that check, therefore they're removed here.
|
| 1337 |
+
* https://github.com/amannn/next-intl/issues/2394
|
| 1338 |
+
*/
|
| 1339 |
+
function pruneUndefined(value) {
|
| 1340 |
+
if (Array.isArray(value)) {
|
| 1341 |
+
return value.map(pruneUndefined);
|
| 1342 |
+
}
|
| 1343 |
+
if (value !== null && typeof value === 'object') {
|
| 1344 |
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined).map(([key, entry]) => [key, pruneUndefined(entry)]));
|
| 1345 |
+
}
|
| 1346 |
+
return value;
|
| 1347 |
+
}
|
| 1348 |
+
function resolveI18nPath(providedPath, cwd) {
|
| 1349 |
+
function resolvePath(pathname) {
|
| 1350 |
+
const parts = [];
|
| 1351 |
+
if (cwd) parts.push(cwd);
|
| 1352 |
+
parts.push(pathname);
|
| 1353 |
+
return path__default.default.resolve(...parts);
|
| 1354 |
+
}
|
| 1355 |
+
function pathExists(pathname) {
|
| 1356 |
+
return fs__default.default.existsSync(resolvePath(pathname));
|
| 1357 |
+
}
|
| 1358 |
+
if (providedPath) {
|
| 1359 |
+
// We use the `isNextDevOrBuild` condition to avoid throwing errors
|
| 1360 |
+
// if `next.config.ts` is read by a non-Next.js process.
|
| 1361 |
+
// https://github.com/amannn/next-intl/discussions/2209#discussioncomment-15650927
|
| 1362 |
+
if (isDevelopmentOrNextBuild && !pathExists(providedPath)) {
|
| 1363 |
+
throwError(`Could not find i18n config at ${providedPath}, please provide a valid path.`);
|
| 1364 |
+
}
|
| 1365 |
+
return providedPath;
|
| 1366 |
+
} else {
|
| 1367 |
+
for (const candidate of [...withExtensions('./i18n/request'), ...withExtensions('./src/i18n/request')]) {
|
| 1368 |
+
if (pathExists(candidate)) {
|
| 1369 |
+
return candidate;
|
| 1370 |
+
}
|
| 1371 |
+
}
|
| 1372 |
+
if (isDevelopmentOrNextBuild) {
|
| 1373 |
+
throwError(`Could not locate request configuration module.\n\nThis path is supported by default: ./(src/)i18n/request.{js,jsx,ts,tsx}\n\nAlternatively, you can specify a custom location in your Next.js config:\n\nconst withNextIntl = createNextIntlPlugin(\n './path/to/i18n/request.tsx'\n);`);
|
| 1374 |
+
}
|
| 1375 |
+
|
| 1376 |
+
// Default as fallback
|
| 1377 |
+
if (pathExists('./src')) {
|
| 1378 |
+
return './src/i18n/request.ts';
|
| 1379 |
+
} else {
|
| 1380 |
+
return './i18n/request.ts';
|
| 1381 |
+
}
|
| 1382 |
+
}
|
| 1383 |
+
}
|
| 1384 |
+
function getNextConfig(pluginConfig, nextConfig, extractorConfig) {
|
| 1385 |
+
const useTurbo = process.env.TURBOPACK != null;
|
| 1386 |
+
|
| 1387 |
+
// `experimental-analyze` doesn’t set the TURBOPACK env param. Since Next.js
|
| 1388 |
+
// 16 doesn't print a warning when we configure both Turbo- and Webpack, just
|
| 1389 |
+
// always configure Turbopack just in case.
|
| 1390 |
+
const shouldConfigureTurbo = useTurbo || isNextJs16OrHigher();
|
| 1391 |
+
const nextIntlConfig = {};
|
| 1392 |
+
let messageLoadPaths = [];
|
| 1393 |
+
if (pluginConfig.experimental?.messages) {
|
| 1394 |
+
messageLoadPaths = normalizeMessagesCatalogPaths(pluginConfig.experimental.messages.path);
|
| 1395 |
+
}
|
| 1396 |
+
function getExtractMessagesLoaderConfig(config) {
|
| 1397 |
+
return {
|
| 1398 |
+
loader: 'next-intl/extractor/extractionLoader',
|
| 1399 |
+
options: pruneUndefined(config)
|
| 1400 |
+
};
|
| 1401 |
+
}
|
| 1402 |
+
function getCatalogLoaderConfig() {
|
| 1403 |
+
const experimental = pluginConfig.experimental;
|
| 1404 |
+
const messages = experimental.messages;
|
| 1405 |
+
// Normalization enforces a source locale before extraction runs.
|
| 1406 |
+
const sourceLocale = messages.sourceLocale ?? (typeof experimental.extract === 'object' ? experimental.extract.sourceLocale : undefined);
|
| 1407 |
+
return {
|
| 1408 |
+
loader: 'next-intl/extractor/catalogLoader',
|
| 1409 |
+
options: pruneUndefined({
|
| 1410 |
+
messages: {
|
| 1411 |
+
format: messages.format,
|
| 1412 |
+
precompile: messages.precompile,
|
| 1413 |
+
sourceLocale
|
| 1414 |
+
}
|
| 1415 |
+
})
|
| 1416 |
+
};
|
| 1417 |
+
}
|
| 1418 |
+
function getTurboRules() {
|
| 1419 |
+
return nextConfig?.turbopack?.rules ||
|
| 1420 |
+
// @ts-expect-error -- For Next.js <16
|
| 1421 |
+
nextConfig?.experimental?.turbo?.rules || {};
|
| 1422 |
+
}
|
| 1423 |
+
function addTurboRule(rules, glob, rule) {
|
| 1424 |
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
| 1425 |
+
if (rules[glob]) {
|
| 1426 |
+
if (Array.isArray(rules[glob])) {
|
| 1427 |
+
rules[glob].push(rule);
|
| 1428 |
+
} else {
|
| 1429 |
+
rules[glob] = [rules[glob], rule];
|
| 1430 |
+
}
|
| 1431 |
+
} else {
|
| 1432 |
+
rules[glob] = rule;
|
| 1433 |
+
}
|
| 1434 |
+
}
|
| 1435 |
+
if (shouldConfigureTurbo) {
|
| 1436 |
+
if (pluginConfig.requestConfig && path__default.default.isAbsolute(pluginConfig.requestConfig)) {
|
| 1437 |
+
throwError("Turbopack support for next-intl currently does not support absolute paths, please provide a relative one (e.g. './src/i18n/config.ts').\n\nFound: " + pluginConfig.requestConfig);
|
| 1438 |
+
}
|
| 1439 |
+
|
| 1440 |
+
// Assign alias for `next-intl/config`
|
| 1441 |
+
const resolveAlias = {
|
| 1442 |
+
// Turbo aliases don't work with absolute
|
| 1443 |
+
// paths (see error handling above)
|
| 1444 |
+
'next-intl/config': resolveI18nPath(pluginConfig.requestConfig)
|
| 1445 |
+
};
|
| 1446 |
+
|
| 1447 |
+
// Add alias for precompiled message formatting
|
| 1448 |
+
if (pluginConfig.experimental?.messages?.precompile) {
|
| 1449 |
+
// Workaround for https://github.com/vercel/next.js/issues/88540
|
| 1450 |
+
let formatOnlyPath = path__default.default.relative(process.cwd(), require$1.resolve('use-intl/format-message/format-only'));
|
| 1451 |
+
|
| 1452 |
+
// Turbopack seems to require this, otherwise `use-intl/format-message` is
|
| 1453 |
+
// still bundled (despite the code correctly calling into `format-only`).
|
| 1454 |
+
// Note that in this monorepo this is not necessary, because we'll end
|
| 1455 |
+
// up with a path like `../…` — but for actual consumers this is required.
|
| 1456 |
+
if (!formatOnlyPath.startsWith('.')) {
|
| 1457 |
+
formatOnlyPath = `./${formatOnlyPath}`;
|
| 1458 |
+
}
|
| 1459 |
+
resolveAlias['use-intl/format-message'] = normalizeTurbopackAliasPath(formatOnlyPath);
|
| 1460 |
+
}
|
| 1461 |
+
|
| 1462 |
+
// Add loaders
|
| 1463 |
+
let rules;
|
| 1464 |
+
|
| 1465 |
+
// Add loader for extractor
|
| 1466 |
+
if (pluginConfig.experimental?.extract) {
|
| 1467 |
+
if (!isNextJs16OrHigher()) {
|
| 1468 |
+
throwError('Message extraction requires Next.js 16 or higher.');
|
| 1469 |
+
}
|
| 1470 |
+
rules ??= getTurboRules();
|
| 1471 |
+
addTurboRule(rules, `*.{${SourceFileFilter.EXTENSIONS.join(',')}}`, {
|
| 1472 |
+
loaders: [getExtractMessagesLoaderConfig(extractorConfig)],
|
| 1473 |
+
condition: {
|
| 1474 |
+
// We don't filter for `path` here to allow transformation
|
| 1475 |
+
// of `useExtracted` calls in external packages (e.g. monorepos)
|
| 1476 |
+
content: /(useExtracted|getExtracted)/
|
| 1477 |
+
}
|
| 1478 |
+
});
|
| 1479 |
+
}
|
| 1480 |
+
|
| 1481 |
+
// Add loader for catalog
|
| 1482 |
+
if (pluginConfig.experimental?.messages) {
|
| 1483 |
+
if (!isNextJs16OrHigher()) {
|
| 1484 |
+
throwError('Message catalog loading requires Next.js 16 or higher.');
|
| 1485 |
+
}
|
| 1486 |
+
rules ??= getTurboRules();
|
| 1487 |
+
const extension = getFormatExtension(pluginConfig.experimental.messages.format);
|
| 1488 |
+
addTurboRule(rules, `*${extension}`, {
|
| 1489 |
+
loaders: [getCatalogLoaderConfig()],
|
| 1490 |
+
condition: {
|
| 1491 |
+
path: `{${messageLoadPaths.join(',')}}/**/*`
|
| 1492 |
+
},
|
| 1493 |
+
as: '*.js'
|
| 1494 |
+
});
|
| 1495 |
+
}
|
| 1496 |
+
if (hasStableTurboConfig() &&
|
| 1497 |
+
// @ts-expect-error -- For Next.js <16
|
| 1498 |
+
!nextConfig?.experimental?.turbo) {
|
| 1499 |
+
nextIntlConfig.turbopack = {
|
| 1500 |
+
...nextConfig?.turbopack,
|
| 1501 |
+
...(rules && {
|
| 1502 |
+
rules
|
| 1503 |
+
}),
|
| 1504 |
+
resolveAlias: {
|
| 1505 |
+
...nextConfig?.turbopack?.resolveAlias,
|
| 1506 |
+
...resolveAlias
|
| 1507 |
+
}
|
| 1508 |
+
};
|
| 1509 |
+
} else {
|
| 1510 |
+
nextIntlConfig.experimental = {
|
| 1511 |
+
...nextConfig?.experimental,
|
| 1512 |
+
// @ts-expect-error -- For Next.js <16
|
| 1513 |
+
turbo: {
|
| 1514 |
+
// @ts-expect-error -- For Next.js <16
|
| 1515 |
+
...nextConfig?.experimental?.turbo,
|
| 1516 |
+
...(rules && {
|
| 1517 |
+
rules
|
| 1518 |
+
}),
|
| 1519 |
+
resolveAlias: {
|
| 1520 |
+
// @ts-expect-error -- For Next.js <16
|
| 1521 |
+
...nextConfig?.experimental?.turbo?.resolveAlias,
|
| 1522 |
+
...resolveAlias
|
| 1523 |
+
}
|
| 1524 |
+
}
|
| 1525 |
+
};
|
| 1526 |
+
}
|
| 1527 |
+
}
|
| 1528 |
+
if (!useTurbo) {
|
| 1529 |
+
nextIntlConfig.webpack = function webpack(config, context) {
|
| 1530 |
+
if (!config.resolve) config.resolve = {};
|
| 1531 |
+
if (!config.resolve.alias) config.resolve.alias = {};
|
| 1532 |
+
|
| 1533 |
+
// Assign alias for `next-intl/config`
|
| 1534 |
+
// (Webpack requires absolute paths)
|
| 1535 |
+
config.resolve.alias['next-intl/config'] = path__default.default.resolve(config.context, resolveI18nPath(pluginConfig.requestConfig, config.context));
|
| 1536 |
+
|
| 1537 |
+
// Add alias for precompiled message formatting
|
| 1538 |
+
if (pluginConfig.experimental?.messages?.precompile) {
|
| 1539 |
+
// Use require.resolve to get the actual file path, since
|
| 1540 |
+
// bundlers don't properly resolve package subpath exports
|
| 1541 |
+
// when used as alias targets
|
| 1542 |
+
config.resolve.alias['use-intl/format-message'] = require$1.resolve('use-intl/format-message/format-only');
|
| 1543 |
+
}
|
| 1544 |
+
|
| 1545 |
+
// Add loader for extractor
|
| 1546 |
+
if (pluginConfig.experimental?.extract) {
|
| 1547 |
+
if (!config.module) config.module = {};
|
| 1548 |
+
if (!config.module.rules) config.module.rules = [];
|
| 1549 |
+
config.module.rules.push({
|
| 1550 |
+
test: new RegExp(`\\.(${SourceFileFilter.EXTENSIONS.join('|')})$`),
|
| 1551 |
+
use: [getExtractMessagesLoaderConfig(extractorConfig)]
|
| 1552 |
+
});
|
| 1553 |
+
}
|
| 1554 |
+
|
| 1555 |
+
// Add loader for catalog
|
| 1556 |
+
if (pluginConfig.experimental?.messages) {
|
| 1557 |
+
if (!config.module) config.module = {};
|
| 1558 |
+
if (!config.module.rules) config.module.rules = [];
|
| 1559 |
+
const extension = getFormatExtension(pluginConfig.experimental.messages.format);
|
| 1560 |
+
config.module.rules.push({
|
| 1561 |
+
test: new RegExp(`${extension.replace(/\./g, '\\.')}$`),
|
| 1562 |
+
include: messageLoadPaths.map(dirPath => path__default.default.resolve(config.context, dirPath)),
|
| 1563 |
+
use: [getCatalogLoaderConfig()],
|
| 1564 |
+
type: 'javascript/auto'
|
| 1565 |
+
});
|
| 1566 |
+
}
|
| 1567 |
+
if (typeof nextConfig?.webpack === 'function') {
|
| 1568 |
+
return nextConfig.webpack(config, context);
|
| 1569 |
+
}
|
| 1570 |
+
return config;
|
| 1571 |
+
};
|
| 1572 |
+
}
|
| 1573 |
+
|
| 1574 |
+
// Forward config
|
| 1575 |
+
const env = {};
|
| 1576 |
+
if (nextConfig?.trailingSlash) {
|
| 1577 |
+
env._next_intl_trailing_slash = 'true';
|
| 1578 |
+
}
|
| 1579 |
+
if (nextConfig?.basePath) {
|
| 1580 |
+
env._next_intl_base_path = nextConfig.basePath;
|
| 1581 |
+
}
|
| 1582 |
+
// Note that `undefined` values are not supported by Turbopack
|
| 1583 |
+
if (Object.keys(env).length > 0) {
|
| 1584 |
+
nextIntlConfig.env = {
|
| 1585 |
+
...nextConfig?.env,
|
| 1586 |
+
...env
|
| 1587 |
+
};
|
| 1588 |
+
}
|
| 1589 |
+
return Object.assign({}, nextConfig, nextIntlConfig);
|
| 1590 |
+
}
|
| 1591 |
+
|
| 1592 |
+
function initPlugin(pluginConfig, nextConfig) {
|
| 1593 |
+
if (nextConfig?.i18n != null) {
|
| 1594 |
+
warn("An `i18n` property was found in your Next.js config. This likely causes conflicts and should therefore be removed if you use the App Router.\n\nIf you're in progress of migrating from the Pages Router, you can refer to this example: https://next-intl.dev/examples#app-router-migration\n");
|
| 1595 |
+
}
|
| 1596 |
+
const skipWatchers = isNextTelemetryDetachedFlushProcess();
|
| 1597 |
+
const messagesPathOrPaths = pluginConfig.experimental?.createMessagesDeclaration;
|
| 1598 |
+
if (messagesPathOrPaths && !skipWatchers) {
|
| 1599 |
+
createMessagesDeclaration(typeof messagesPathOrPaths === 'string' ? [messagesPathOrPaths] : messagesPathOrPaths);
|
| 1600 |
+
}
|
| 1601 |
+
let extractorConfig;
|
| 1602 |
+
const experimental = pluginConfig.experimental;
|
| 1603 |
+
const extract = experimental?.extract;
|
| 1604 |
+
if (extract) {
|
| 1605 |
+
extractorConfig = normalizeExtractorConfig({
|
| 1606 |
+
extract,
|
| 1607 |
+
messages: experimental.messages,
|
| 1608 |
+
srcPath: experimental.srcPath
|
| 1609 |
+
});
|
| 1610 |
+
}
|
| 1611 |
+
if (!skipWatchers) {
|
| 1612 |
+
initExtractionCompiler(extractorConfig);
|
| 1613 |
+
}
|
| 1614 |
+
return getNextConfig(pluginConfig, nextConfig, extractorConfig);
|
| 1615 |
+
}
|
| 1616 |
+
function createNextIntlPlugin(i18nPathOrConfig = {}) {
|
| 1617 |
+
const config = typeof i18nPathOrConfig === 'string' ? {
|
| 1618 |
+
requestConfig: i18nPathOrConfig
|
| 1619 |
+
} : i18nPathOrConfig;
|
| 1620 |
+
return function withNextIntl(nextConfig) {
|
| 1621 |
+
return initPlugin(config, nextConfig);
|
| 1622 |
+
};
|
| 1623 |
+
}
|
| 1624 |
+
|
| 1625 |
+
/**
|
| 1626 |
+
* Next runs `telemetry/detached-flush.js` in a detached process to flush telemetry
|
| 1627 |
+
* (often when `next dev` exits). That loads dev `next.config` with inherited
|
| 1628 |
+
* `NODE_ENV=development`, which would otherwise start orphan plugin watchers.
|
| 1629 |
+
*/
|
| 1630 |
+
function isNextTelemetryDetachedFlushProcess() {
|
| 1631 |
+
const scriptPath = process.argv[1];
|
| 1632 |
+
if (!scriptPath) return false;
|
| 1633 |
+
const normalized = scriptPath.replace(/\\/g, '/');
|
| 1634 |
+
return normalized.includes('/telemetry/detached-flush');
|
| 1635 |
+
}
|
| 1636 |
+
|
| 1637 |
+
module.exports = createNextIntlPlugin;
|
e-commerce/node_modules/next-intl/dist/esm/development/config.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function getConfig() {
|
| 2 |
+
throw new Error("Couldn't find next-intl config file. Please follow the instructions at https://next-intl.dev/docs/getting-started/app-router");
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
export { getConfig as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { default as unstable_extractMessages } from './extractor/extractMessages.js';
|
| 2 |
+
export { defineCodec } from './extractor/format/ExtractorCodec.js';
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/ExtractionCompiler.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import CatalogManager from './catalog/CatalogManager.js';
|
| 2 |
+
import MessageExtractor from './extractor/MessageExtractor.js';
|
| 3 |
+
|
| 4 |
+
class ExtractionCompiler {
|
| 5 |
+
constructor(config, opts = {}) {
|
| 6 |
+
const extractor = opts.extractor ?? new MessageExtractor(opts);
|
| 7 |
+
this.manager = new CatalogManager(config, {
|
| 8 |
+
...opts,
|
| 9 |
+
extractor
|
| 10 |
+
});
|
| 11 |
+
this[Symbol.dispose] = this[Symbol.dispose].bind(this);
|
| 12 |
+
this.installExitHandlers();
|
| 13 |
+
}
|
| 14 |
+
async extractAll() {
|
| 15 |
+
// We can't rely on all files being compiled (e.g. due to persistent
|
| 16 |
+
// caching), so loading the messages initially is necessary.
|
| 17 |
+
await this.manager.loadMessages();
|
| 18 |
+
await this.manager.save();
|
| 19 |
+
}
|
| 20 |
+
[Symbol.dispose]() {
|
| 21 |
+
this.uninstallExitHandlers();
|
| 22 |
+
this.manager[Symbol.dispose]();
|
| 23 |
+
}
|
| 24 |
+
installExitHandlers() {
|
| 25 |
+
const cleanup = this[Symbol.dispose];
|
| 26 |
+
process.on('exit', cleanup);
|
| 27 |
+
process.on('SIGINT', cleanup);
|
| 28 |
+
process.on('SIGTERM', cleanup);
|
| 29 |
+
}
|
| 30 |
+
uninstallExitHandlers() {
|
| 31 |
+
const cleanup = this[Symbol.dispose];
|
| 32 |
+
process.off('exit', cleanup);
|
| 33 |
+
process.off('SIGINT', cleanup);
|
| 34 |
+
process.off('SIGTERM', cleanup);
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export { ExtractionCompiler as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/CatalogLocales.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs$1 from 'fs';
|
| 2 |
+
import fs from 'fs/promises';
|
| 3 |
+
import path from 'path';
|
| 4 |
+
|
| 5 |
+
class CatalogLocales {
|
| 6 |
+
onChangeCallbacks = new Set();
|
| 7 |
+
constructor(params) {
|
| 8 |
+
this.messagesDir = params.messagesDir;
|
| 9 |
+
this.sourceLocale = params.sourceLocale;
|
| 10 |
+
this.extension = params.extension;
|
| 11 |
+
this.locales = params.locales;
|
| 12 |
+
}
|
| 13 |
+
async getTargetLocales() {
|
| 14 |
+
if (this.targetLocales) {
|
| 15 |
+
return this.targetLocales;
|
| 16 |
+
}
|
| 17 |
+
if (this.locales === 'infer') {
|
| 18 |
+
this.targetLocales = await this.readTargetLocales();
|
| 19 |
+
} else {
|
| 20 |
+
this.targetLocales = this.locales.filter(locale => locale !== this.sourceLocale);
|
| 21 |
+
}
|
| 22 |
+
return this.targetLocales;
|
| 23 |
+
}
|
| 24 |
+
async readTargetLocales() {
|
| 25 |
+
try {
|
| 26 |
+
const files = await fs.readdir(this.messagesDir);
|
| 27 |
+
return files.filter(file => file.endsWith(this.extension)).map(file => path.basename(file, this.extension)).filter(locale => locale !== this.sourceLocale);
|
| 28 |
+
} catch {
|
| 29 |
+
return [];
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
subscribeLocalesChange(callback) {
|
| 33 |
+
this.onChangeCallbacks.add(callback);
|
| 34 |
+
if (this.locales === 'infer' && !this.watcher) {
|
| 35 |
+
void this.startWatcher();
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
unsubscribeLocalesChange(callback) {
|
| 39 |
+
this.onChangeCallbacks.delete(callback);
|
| 40 |
+
if (this.onChangeCallbacks.size === 0) {
|
| 41 |
+
this.stopWatcher();
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
async startWatcher() {
|
| 45 |
+
if (this.watcher) {
|
| 46 |
+
return;
|
| 47 |
+
}
|
| 48 |
+
await fs.mkdir(this.messagesDir, {
|
| 49 |
+
recursive: true
|
| 50 |
+
});
|
| 51 |
+
this.watcher = fs$1.watch(this.messagesDir, {
|
| 52 |
+
persistent: false,
|
| 53 |
+
recursive: false
|
| 54 |
+
}, (event, filename) => {
|
| 55 |
+
const isCatalogFile = filename != null && filename.endsWith(this.extension) && !filename.includes(path.sep);
|
| 56 |
+
if (isCatalogFile) {
|
| 57 |
+
void this.onChange();
|
| 58 |
+
}
|
| 59 |
+
});
|
| 60 |
+
}
|
| 61 |
+
stopWatcher() {
|
| 62 |
+
if (this.watcher) {
|
| 63 |
+
this.watcher.close();
|
| 64 |
+
this.watcher = undefined;
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
async onChange() {
|
| 68 |
+
const oldLocales = new Set(this.targetLocales || []);
|
| 69 |
+
this.targetLocales = await this.readTargetLocales();
|
| 70 |
+
const newLocalesSet = new Set(this.targetLocales);
|
| 71 |
+
const added = this.targetLocales.filter(locale => !oldLocales.has(locale));
|
| 72 |
+
const removed = Array.from(oldLocales).filter(locale => !newLocalesSet.has(locale));
|
| 73 |
+
if (added.length > 0 || removed.length > 0) {
|
| 74 |
+
for (const callback of this.onChangeCallbacks) {
|
| 75 |
+
callback({
|
| 76 |
+
added,
|
| 77 |
+
removed
|
| 78 |
+
});
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
export { CatalogLocales as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/CatalogManager.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'fs/promises';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
import { resolveCodec, getFormatExtension } from '../format/index.js';
|
| 4 |
+
import SourceFileScanner from '../source/SourceFileScanner.js';
|
| 5 |
+
import SourceFileWatcher from '../source/SourceFileWatcher.js';
|
| 6 |
+
import { getDefaultProjectRoot, localeCompare, compareReferences } from '../utils.js';
|
| 7 |
+
import CatalogLocales from './CatalogLocales.js';
|
| 8 |
+
import CatalogPersister from './CatalogPersister.js';
|
| 9 |
+
import SaveScheduler from './SaveScheduler.js';
|
| 10 |
+
|
| 11 |
+
class CatalogManager {
|
| 12 |
+
/**
|
| 13 |
+
* Extraction-derived fields aggregated into `ExtractorMessage`.
|
| 14 |
+
* Source code is the source of truth for these fields, only ancillary
|
| 15 |
+
* codec fields may merge from disk (e.g. flags).
|
| 16 |
+
*/
|
| 17 |
+
static extractorOwnedAggregatorKeys = new Set(['description', 'id', 'message', 'references']);
|
| 18 |
+
/**
|
| 19 |
+
* Source of truth for statically extracted source messages,
|
| 20 |
+
* grouped by file and message ID.
|
| 21 |
+
*/
|
| 22 |
+
sourceMessagesByFile = new Map();
|
| 23 |
+
|
| 24 |
+
/**
|
| 25 |
+
* Reverse index for rebuilding aggregated messages without scanning all files.
|
| 26 |
+
* Contains the same `SourceExtractedMessage` arrays as `sourceMessagesByFile` and is
|
| 27 |
+
* kept in sync with it.
|
| 28 |
+
*/
|
| 29 |
+
sourceMessagesById = new Map();
|
| 30 |
+
|
| 31 |
+
/**
|
| 32 |
+
* Fast lookup for messages by ID, aggregated across all files. This combines
|
| 33 |
+
* metadata from `sourceMessagesById`, e.g. references and descriptions.
|
| 34 |
+
*/
|
| 35 |
+
messagesById = new Map();
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* This potentially also includes outdated ones that were initially available,
|
| 39 |
+
* but are not used anymore. This allows to restore them if they are used again.
|
| 40 |
+
**/
|
| 41 |
+
translationsByTargetLocale = new Map();
|
| 42 |
+
lastWriteByLocale = new Map();
|
| 43 |
+
|
| 44 |
+
// Cached instances
|
| 45 |
+
|
| 46 |
+
// Resolves when all catalogs are loaded
|
| 47 |
+
|
| 48 |
+
// Resolves when the initial project scan and processing is complete
|
| 49 |
+
|
| 50 |
+
constructor(config, opts) {
|
| 51 |
+
this.config = config;
|
| 52 |
+
this.saveScheduler = new SaveScheduler(opts.saveDebounceMs ?? 50);
|
| 53 |
+
this.projectRoot = opts.projectRoot ?? getDefaultProjectRoot();
|
| 54 |
+
this.isDevelopment = opts.isDevelopment ?? false;
|
| 55 |
+
this.extractor = opts.extractor;
|
| 56 |
+
if (this.isDevelopment) {
|
| 57 |
+
// We kick this off as early as possible, so we get notified about changes
|
| 58 |
+
// that happen during the initial project scan (while awaiting it to
|
| 59 |
+
// complete though)
|
| 60 |
+
this.sourceWatcher = new SourceFileWatcher(this.getSrcPaths(), this.handleFileEvents.bind(this));
|
| 61 |
+
void this.sourceWatcher.start();
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
async getCodec() {
|
| 65 |
+
if (!this.codec) {
|
| 66 |
+
this.codec = await resolveCodec(this.config.messages.format, this.projectRoot);
|
| 67 |
+
}
|
| 68 |
+
return this.codec;
|
| 69 |
+
}
|
| 70 |
+
async getPersister() {
|
| 71 |
+
if (this.persister) {
|
| 72 |
+
return this.persister;
|
| 73 |
+
} else {
|
| 74 |
+
this.persister = new CatalogPersister({
|
| 75 |
+
messagesPath: this.config.extract.path,
|
| 76 |
+
codec: await this.getCodec(),
|
| 77 |
+
extension: getFormatExtension(this.config.messages.format),
|
| 78 |
+
sourceLocale: this.config.extract.sourceLocale
|
| 79 |
+
});
|
| 80 |
+
return this.persister;
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
getCatalogLocales() {
|
| 84 |
+
if (this.catalogLocales) {
|
| 85 |
+
return this.catalogLocales;
|
| 86 |
+
} else {
|
| 87 |
+
const messagesDir = path.join(this.projectRoot, this.config.extract.path);
|
| 88 |
+
this.catalogLocales = new CatalogLocales({
|
| 89 |
+
messagesDir,
|
| 90 |
+
extension: getFormatExtension(this.config.messages.format),
|
| 91 |
+
locales: this.config.extract.locales,
|
| 92 |
+
sourceLocale: this.config.extract.sourceLocale
|
| 93 |
+
});
|
| 94 |
+
return this.catalogLocales;
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
async getTargetLocales() {
|
| 98 |
+
return this.getCatalogLocales().getTargetLocales();
|
| 99 |
+
}
|
| 100 |
+
getSrcPaths() {
|
| 101 |
+
return (Array.isArray(this.config.extract.srcPath) ? this.config.extract.srcPath : [this.config.extract.srcPath]).map(srcPath => path.join(this.projectRoot, srcPath));
|
| 102 |
+
}
|
| 103 |
+
async loadMessages() {
|
| 104 |
+
const sourceDiskMessages = await this.loadSourceMessages();
|
| 105 |
+
this.loadCatalogsPromise = this.loadTargetMessages();
|
| 106 |
+
await this.loadCatalogsPromise;
|
| 107 |
+
this.scanCompletePromise = (async () => {
|
| 108 |
+
const sourceFiles = Array.from(await SourceFileScanner.getSourceFiles(this.getSrcPaths()))
|
| 109 |
+
// Stable file order keeps catalog ties independent of processing timing
|
| 110 |
+
.toSorted(localeCompare);
|
| 111 |
+
const extractedFiles = await Promise.all(sourceFiles.map(async filePath => ({
|
| 112 |
+
filePath,
|
| 113 |
+
messages: await this.extractFile(filePath)
|
| 114 |
+
})));
|
| 115 |
+
for (const {
|
| 116 |
+
filePath,
|
| 117 |
+
messages
|
| 118 |
+
} of extractedFiles) {
|
| 119 |
+
if (messages) {
|
| 120 |
+
this.applyFileMessages(filePath, messages);
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
this.mergeSourceDiskMetadata(sourceDiskMessages);
|
| 124 |
+
})();
|
| 125 |
+
await this.scanCompletePromise;
|
| 126 |
+
if (this.isDevelopment) {
|
| 127 |
+
const catalogLocales = this.getCatalogLocales();
|
| 128 |
+
catalogLocales.subscribeLocalesChange(this.onLocalesChange);
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
async loadSourceMessages() {
|
| 132 |
+
// Load source catalog to hydrate metadata (e.g. flags) later without
|
| 133 |
+
// treating catalog entries as source of truth.
|
| 134 |
+
const diskMessages = await this.loadLocaleMessages(this.config.extract.sourceLocale);
|
| 135 |
+
const byId = new Map();
|
| 136 |
+
for (const diskMessage of diskMessages) {
|
| 137 |
+
byId.set(diskMessage.id, diskMessage);
|
| 138 |
+
}
|
| 139 |
+
return byId;
|
| 140 |
+
}
|
| 141 |
+
async loadLocaleMessages(locale) {
|
| 142 |
+
const persister = await this.getPersister();
|
| 143 |
+
const messages = await persister.read(locale);
|
| 144 |
+
const fileTime = await persister.getLastModified(locale);
|
| 145 |
+
this.lastWriteByLocale.set(locale, fileTime);
|
| 146 |
+
return messages;
|
| 147 |
+
}
|
| 148 |
+
async loadTargetMessages() {
|
| 149 |
+
const targetLocales = await this.getTargetLocales();
|
| 150 |
+
await Promise.all(targetLocales.map(locale => this.reloadLocaleCatalog(locale)));
|
| 151 |
+
}
|
| 152 |
+
async reloadLocaleCatalog(locale) {
|
| 153 |
+
const diskMessages = await this.loadLocaleMessages(locale);
|
| 154 |
+
if (locale === this.config.extract.sourceLocale) {
|
| 155 |
+
// For source: Merge additional properties like flags
|
| 156 |
+
for (const diskMessage of diskMessages) {
|
| 157 |
+
const prev = this.messagesById.get(diskMessage.id);
|
| 158 |
+
if (prev) {
|
| 159 |
+
for (const key of Object.keys(diskMessage)) {
|
| 160 |
+
if (!CatalogManager.extractorOwnedAggregatorKeys.has(key)) {
|
| 161 |
+
// For unknown properties (like flags), disk wins
|
| 162 |
+
prev[key] = diskMessage[key];
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
} else {
|
| 168 |
+
// For target: disk wins completely, BUT preserve existing translations
|
| 169 |
+
// if we read empty (likely a write in progress by an external tool
|
| 170 |
+
// that causes the file to temporarily be empty)
|
| 171 |
+
const existingTranslations = this.translationsByTargetLocale.get(locale);
|
| 172 |
+
const hasExistingTranslations = existingTranslations && existingTranslations.size > 0;
|
| 173 |
+
if (diskMessages.length > 0) {
|
| 174 |
+
// We got content from disk, replace with it
|
| 175 |
+
const translations = new Map();
|
| 176 |
+
for (const message of diskMessages) {
|
| 177 |
+
translations.set(message.id, message);
|
| 178 |
+
}
|
| 179 |
+
this.translationsByTargetLocale.set(locale, translations);
|
| 180 |
+
} else if (hasExistingTranslations) ; else {
|
| 181 |
+
// We read empty and have no existing translations
|
| 182 |
+
const translations = new Map();
|
| 183 |
+
this.translationsByTargetLocale.set(locale, translations);
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
mergeSourceDiskMetadata(diskMessages) {
|
| 188 |
+
for (const [id, diskMessage] of diskMessages) {
|
| 189 |
+
const existing = this.messagesById.get(id);
|
| 190 |
+
if (!existing) continue;
|
| 191 |
+
|
| 192 |
+
// Fill unknown metadata from disk without replacing extraction-owned fields.
|
| 193 |
+
for (const key of Object.keys(diskMessage)) {
|
| 194 |
+
if (!CatalogManager.extractorOwnedAggregatorKeys.has(key) && existing[key] == null) {
|
| 195 |
+
existing[key] = diskMessage[key];
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
async processFile(absoluteFilePath) {
|
| 201 |
+
const messages = await this.extractFile(absoluteFilePath);
|
| 202 |
+
|
| 203 |
+
// `undefined` only when `extractFile()` throws. An empty array is success
|
| 204 |
+
// and must still run `applyFileMessages` to clear stale ids for this file.
|
| 205 |
+
if (!messages) return false;
|
| 206 |
+
return this.applyFileMessages(absoluteFilePath, messages);
|
| 207 |
+
}
|
| 208 |
+
async extractFile(absoluteFilePath) {
|
| 209 |
+
let messages = [];
|
| 210 |
+
try {
|
| 211 |
+
const content = await fs.readFile(absoluteFilePath, 'utf8');
|
| 212 |
+
let extraction;
|
| 213 |
+
try {
|
| 214 |
+
extraction = await this.extractor.extract(absoluteFilePath, content);
|
| 215 |
+
} catch {
|
| 216 |
+
return undefined;
|
| 217 |
+
}
|
| 218 |
+
messages = extraction.messages;
|
| 219 |
+
} catch (err) {
|
| 220 |
+
if (err.code !== 'ENOENT') {
|
| 221 |
+
throw err;
|
| 222 |
+
}
|
| 223 |
+
// ENOENT -> treat as no messages
|
| 224 |
+
}
|
| 225 |
+
return messages;
|
| 226 |
+
}
|
| 227 |
+
applyFileMessages(absoluteFilePath, messages) {
|
| 228 |
+
const prevFileMessages = this.sourceMessagesByFile.get(absoluteFilePath);
|
| 229 |
+
const nextFileMessages = this.groupSourceMessagesById(messages);
|
| 230 |
+
const affectedIds = new Set([...(prevFileMessages?.keys() ?? []), ...nextFileMessages.keys()]);
|
| 231 |
+
if (nextFileMessages.size > 0) {
|
| 232 |
+
this.sourceMessagesByFile.set(absoluteFilePath, nextFileMessages);
|
| 233 |
+
} else {
|
| 234 |
+
this.sourceMessagesByFile.delete(absoluteFilePath);
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
// Clear this file's contribution from the reverse index, then re-insert
|
| 238 |
+
// fresh rows and rebuild aggregates (messagesById) per touched id.
|
| 239 |
+
for (const id of affectedIds) {
|
| 240 |
+
const sourceMessagesForId = this.sourceMessagesById.get(id);
|
| 241 |
+
if (sourceMessagesForId) {
|
| 242 |
+
sourceMessagesForId.delete(absoluteFilePath);
|
| 243 |
+
// No files left for this id: drop the reverse-index entry.
|
| 244 |
+
if (sourceMessagesForId.size === 0) {
|
| 245 |
+
this.sourceMessagesById.delete(id);
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
const nextSourceMessagesForId = nextFileMessages.get(id);
|
| 249 |
+
if (nextSourceMessagesForId) {
|
| 250 |
+
let sourceMessagesByFile = this.sourceMessagesById.get(id);
|
| 251 |
+
if (!sourceMessagesByFile) {
|
| 252 |
+
sourceMessagesByFile = new Map();
|
| 253 |
+
this.sourceMessagesById.set(id, sourceMessagesByFile);
|
| 254 |
+
}
|
| 255 |
+
sourceMessagesByFile.set(absoluteFilePath, nextSourceMessagesForId);
|
| 256 |
+
}
|
| 257 |
+
this.rebuildMessageById(id);
|
| 258 |
+
}
|
| 259 |
+
const changed = this.haveMessagesChangedForFile(prevFileMessages, nextFileMessages);
|
| 260 |
+
return changed;
|
| 261 |
+
}
|
| 262 |
+
groupSourceMessagesById(messages) {
|
| 263 |
+
const result = new Map();
|
| 264 |
+
for (const message of messages) {
|
| 265 |
+
const messagesById = result.get(message.id);
|
| 266 |
+
if (messagesById) {
|
| 267 |
+
messagesById.push(message);
|
| 268 |
+
} else {
|
| 269 |
+
result.set(message.id, [message]);
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
return result;
|
| 273 |
+
}
|
| 274 |
+
rebuildMessageById(id) {
|
| 275 |
+
const sourceMessages = Array.from(this.sourceMessagesById.get(id)?.values() ?? []).flat();
|
| 276 |
+
if (sourceMessages.length === 0) {
|
| 277 |
+
this.messagesById.delete(id);
|
| 278 |
+
return;
|
| 279 |
+
}
|
| 280 |
+
const previousMessage = this.messagesById.get(id);
|
| 281 |
+
const aggregate = {
|
| 282 |
+
description: this.mergeDescriptions(sourceMessages),
|
| 283 |
+
id,
|
| 284 |
+
message: sourceMessages[0].message,
|
| 285 |
+
references: sourceMessages.map(message => message.reference).sort(compareReferences)
|
| 286 |
+
};
|
| 287 |
+
if (previousMessage) {
|
| 288 |
+
for (const key of Object.keys(previousMessage)) {
|
| 289 |
+
// Preserve extra fields (e.g. from disk/codec) across rebuilds; the
|
| 290 |
+
// four core fields above are always recomputed from source messages.
|
| 291 |
+
if (!CatalogManager.extractorOwnedAggregatorKeys.has(key) && aggregate[key] == null) {
|
| 292 |
+
aggregate[key] = previousMessage[key];
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
this.messagesById.set(id, aggregate);
|
| 297 |
+
}
|
| 298 |
+
mergeDescriptions(messages) {
|
| 299 |
+
const sortedByReference = messages.toSorted((a, b) => compareReferences(a.reference, b.reference));
|
| 300 |
+
const merged = [];
|
| 301 |
+
for (const message of sortedByReference) {
|
| 302 |
+
const {
|
| 303 |
+
description
|
| 304 |
+
} = message;
|
| 305 |
+
if (description != null && !merged.includes(description)) {
|
| 306 |
+
merged.push(description);
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
return merged;
|
| 310 |
+
}
|
| 311 |
+
haveMessagesChangedForFile(beforeMessages, afterMessages) {
|
| 312 |
+
// If one exists and the other doesn't, there's a change
|
| 313 |
+
if (!beforeMessages) {
|
| 314 |
+
return afterMessages.size > 0;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
// Different sizes means changes
|
| 318 |
+
if (beforeMessages.size !== afterMessages.size) {
|
| 319 |
+
return true;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
// Check differences in beforeMessages vs afterMessages
|
| 323 |
+
for (const [id, prevSourceMessages] of beforeMessages) {
|
| 324 |
+
const nextSourceMessages = afterMessages.get(id);
|
| 325 |
+
if (!nextSourceMessages) {
|
| 326 |
+
return true;
|
| 327 |
+
}
|
| 328 |
+
if (!this.areSourceMessageArraysEqual(prevSourceMessages, nextSourceMessages)) {
|
| 329 |
+
return true; // Early exit on first difference
|
| 330 |
+
}
|
| 331 |
+
}
|
| 332 |
+
return false;
|
| 333 |
+
}
|
| 334 |
+
areSourceMessageArraysEqual(messages1, messages2) {
|
| 335 |
+
return messages1.length === messages2.length && messages1.every((message, index) => this.areSourceMessagesEqual(message, messages2[index]));
|
| 336 |
+
}
|
| 337 |
+
areSourceMessagesEqual(msg1, msg2) {
|
| 338 |
+
return msg1.id === msg2.id && msg1.message === msg2.message && msg1.description === msg2.description && msg1.reference.path === msg2.reference.path && msg1.reference.line === msg2.reference.line && msg1.reference.column === msg2.reference.column;
|
| 339 |
+
}
|
| 340 |
+
async save() {
|
| 341 |
+
return this.saveScheduler.schedule(() => this.saveImpl());
|
| 342 |
+
}
|
| 343 |
+
async saveImpl() {
|
| 344 |
+
await this.saveLocale(this.config.extract.sourceLocale);
|
| 345 |
+
const targetLocales = await this.getTargetLocales();
|
| 346 |
+
await Promise.all(targetLocales.map(locale => this.saveLocale(locale)));
|
| 347 |
+
}
|
| 348 |
+
async saveLocale(locale) {
|
| 349 |
+
await this.loadCatalogsPromise;
|
| 350 |
+
const messages = Array.from(this.messagesById.values());
|
| 351 |
+
const persister = await this.getPersister();
|
| 352 |
+
const isSourceLocale = locale === this.config.extract.sourceLocale;
|
| 353 |
+
|
| 354 |
+
// Check if file was modified externally (poll-at-save is cheaper than
|
| 355 |
+
// watchers here since stat() is fast and avoids continuous overhead)
|
| 356 |
+
const lastWriteTime = this.lastWriteByLocale.get(locale);
|
| 357 |
+
const currentFileTime = await persister.getLastModified(locale);
|
| 358 |
+
if (currentFileTime && lastWriteTime && currentFileTime > lastWriteTime) {
|
| 359 |
+
await this.reloadLocaleCatalog(locale);
|
| 360 |
+
}
|
| 361 |
+
const localeMessages = isSourceLocale ? this.messagesById : this.translationsByTargetLocale.get(locale);
|
| 362 |
+
const messagesToPersist = messages.map(message => {
|
| 363 |
+
const localeMessage = localeMessages?.get(message.id);
|
| 364 |
+
return {
|
| 365 |
+
...localeMessage,
|
| 366 |
+
id: message.id,
|
| 367 |
+
description: message.description,
|
| 368 |
+
references: message.references,
|
| 369 |
+
message: isSourceLocale ? message.message : localeMessage?.message ?? ''
|
| 370 |
+
};
|
| 371 |
+
});
|
| 372 |
+
await persister.write(messagesToPersist, {
|
| 373 |
+
locale,
|
| 374 |
+
sourceMessagesById: this.messagesById
|
| 375 |
+
});
|
| 376 |
+
|
| 377 |
+
// Update timestamps
|
| 378 |
+
const newTime = await persister.getLastModified(locale);
|
| 379 |
+
this.lastWriteByLocale.set(locale, newTime);
|
| 380 |
+
}
|
| 381 |
+
onLocalesChange = async params => {
|
| 382 |
+
// Chain to existing promise
|
| 383 |
+
this.loadCatalogsPromise = Promise.all([this.loadCatalogsPromise, ...params.added.map(locale => this.reloadLocaleCatalog(locale))]);
|
| 384 |
+
for (const locale of params.added) {
|
| 385 |
+
await this.saveLocale(locale);
|
| 386 |
+
}
|
| 387 |
+
for (const locale of params.removed) {
|
| 388 |
+
this.translationsByTargetLocale.delete(locale);
|
| 389 |
+
this.lastWriteByLocale.delete(locale);
|
| 390 |
+
}
|
| 391 |
+
};
|
| 392 |
+
async handleFileEvents(events) {
|
| 393 |
+
if (this.loadCatalogsPromise) {
|
| 394 |
+
await this.loadCatalogsPromise;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
// Wait for initial scan to complete to avoid race conditions
|
| 398 |
+
if (this.scanCompletePromise) {
|
| 399 |
+
await this.scanCompletePromise;
|
| 400 |
+
}
|
| 401 |
+
let changed = false;
|
| 402 |
+
const expandedEvents = await this.sourceWatcher.expandDirectoryDeleteEvents(events, Array.from(this.sourceMessagesByFile.keys()));
|
| 403 |
+
// Stable file order keeps catalog ties independent of event timing.
|
| 404 |
+
for (const event of expandedEvents.toSorted((a, b) => localeCompare(a.path, b.path))) {
|
| 405 |
+
const hasChanged = await this.processFile(event.path);
|
| 406 |
+
changed ||= hasChanged;
|
| 407 |
+
}
|
| 408 |
+
if (changed) {
|
| 409 |
+
await this.save();
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
[Symbol.dispose]() {
|
| 413 |
+
this.sourceWatcher?.stop();
|
| 414 |
+
this.sourceWatcher = undefined;
|
| 415 |
+
this.saveScheduler[Symbol.dispose]();
|
| 416 |
+
if (this.catalogLocales && this.isDevelopment) {
|
| 417 |
+
this.catalogLocales.unsubscribeLocalesChange(this.onLocalesChange);
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
export { CatalogManager as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/CatalogPersister.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'fs/promises';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
import { warnAboutMissingReferences } from '../utils.js';
|
| 4 |
+
|
| 5 |
+
class CatalogPersister {
|
| 6 |
+
constructor(params) {
|
| 7 |
+
this.messagesPath = params.messagesPath;
|
| 8 |
+
this.codec = params.codec;
|
| 9 |
+
this.extension = params.extension;
|
| 10 |
+
this.sourceLocale = params.sourceLocale;
|
| 11 |
+
}
|
| 12 |
+
getFileName(locale) {
|
| 13 |
+
return locale + this.extension;
|
| 14 |
+
}
|
| 15 |
+
getFilePath(locale) {
|
| 16 |
+
return path.join(this.messagesPath, this.getFileName(locale));
|
| 17 |
+
}
|
| 18 |
+
async read(locale) {
|
| 19 |
+
const filePath = this.getFilePath(locale);
|
| 20 |
+
let content;
|
| 21 |
+
try {
|
| 22 |
+
content = await fs.readFile(filePath, 'utf8');
|
| 23 |
+
} catch (error) {
|
| 24 |
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
| 25 |
+
return [];
|
| 26 |
+
}
|
| 27 |
+
throw new Error(`Error while reading ${this.getFileName(locale)}:\n> ${error}`, {
|
| 28 |
+
cause: error
|
| 29 |
+
});
|
| 30 |
+
}
|
| 31 |
+
try {
|
| 32 |
+
const messages = this.codec.decode(content, {
|
| 33 |
+
locale,
|
| 34 |
+
sourceLocale: this.sourceLocale
|
| 35 |
+
});
|
| 36 |
+
assertNoArrayMessages(messages);
|
| 37 |
+
return messages;
|
| 38 |
+
} catch (error) {
|
| 39 |
+
throw new Error(`Error while decoding ${this.getFileName(locale)}:\n> ${error}`, {
|
| 40 |
+
cause: error
|
| 41 |
+
});
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
async write(messages, context) {
|
| 45 |
+
const filePath = this.getFilePath(context.locale);
|
| 46 |
+
warnAboutMissingReferences(messages);
|
| 47 |
+
const content = this.codec.encode(messages, context);
|
| 48 |
+
try {
|
| 49 |
+
const outputDir = path.dirname(filePath);
|
| 50 |
+
await fs.mkdir(outputDir, {
|
| 51 |
+
recursive: true
|
| 52 |
+
});
|
| 53 |
+
await fs.writeFile(filePath, content);
|
| 54 |
+
} catch (error) {
|
| 55 |
+
console.error(`❌ Failed to write catalog: ${error}`);
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
async getLastModified(locale) {
|
| 59 |
+
const filePath = this.getFilePath(locale);
|
| 60 |
+
try {
|
| 61 |
+
const stats = await fs.stat(filePath);
|
| 62 |
+
return stats.mtime;
|
| 63 |
+
} catch {
|
| 64 |
+
return undefined;
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// The codecs read arrays fine (numeric path segments), but the runtime only
|
| 70 |
+
// supports strings — enforced here where the runtime's rules apply.
|
| 71 |
+
function assertNoArrayMessages(messages) {
|
| 72 |
+
for (const message of messages) {
|
| 73 |
+
const segments = message.segments;
|
| 74 |
+
if (!Array.isArray(segments)) continue;
|
| 75 |
+
const firstIndex = segments.findIndex(segment => typeof segment === 'number');
|
| 76 |
+
if (firstIndex === -1) continue;
|
| 77 |
+
const arrayPath = segments.slice(0, firstIndex).join('.');
|
| 78 |
+
throw new Error(`Message at \`${arrayPath}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
export { CatalogPersister as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalog/SaveScheduler.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* De-duplicates excessive save invocations,
|
| 3 |
+
* while keeping a single one instant.
|
| 4 |
+
*/
|
| 5 |
+
class SaveScheduler {
|
| 6 |
+
isSaving = false;
|
| 7 |
+
pendingResolvers = [];
|
| 8 |
+
constructor(delayMs = 50) {
|
| 9 |
+
this.delayMs = delayMs;
|
| 10 |
+
}
|
| 11 |
+
async schedule(saveTask) {
|
| 12 |
+
return new Promise((resolve, reject) => {
|
| 13 |
+
this.pendingResolvers.push({
|
| 14 |
+
resolve,
|
| 15 |
+
reject
|
| 16 |
+
});
|
| 17 |
+
this.nextSaveTask = saveTask;
|
| 18 |
+
if (!this.isSaving && !this.saveTimeout) {
|
| 19 |
+
// Not currently saving and no scheduled save, save immediately
|
| 20 |
+
this.executeSave();
|
| 21 |
+
} else if (this.saveTimeout) {
|
| 22 |
+
// A save is already scheduled, reschedule to debounce
|
| 23 |
+
this.scheduleSave();
|
| 24 |
+
}
|
| 25 |
+
// If isSaving is true and no timeout is scheduled, the current save
|
| 26 |
+
// will check for pending resolvers when it completes and schedule
|
| 27 |
+
// another save if needed (see finally block in executeSave)
|
| 28 |
+
});
|
| 29 |
+
}
|
| 30 |
+
scheduleSave() {
|
| 31 |
+
if (this.saveTimeout) {
|
| 32 |
+
clearTimeout(this.saveTimeout);
|
| 33 |
+
}
|
| 34 |
+
this.saveTimeout = setTimeout(() => {
|
| 35 |
+
this.saveTimeout = undefined;
|
| 36 |
+
this.executeSave();
|
| 37 |
+
}, this.delayMs);
|
| 38 |
+
}
|
| 39 |
+
async executeSave() {
|
| 40 |
+
if (this.isSaving) {
|
| 41 |
+
return;
|
| 42 |
+
}
|
| 43 |
+
const saveTask = this.nextSaveTask;
|
| 44 |
+
if (!saveTask) {
|
| 45 |
+
return;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
// Capture current pending resolvers for this save
|
| 49 |
+
const resolversForThisSave = this.pendingResolvers;
|
| 50 |
+
this.pendingResolvers = [];
|
| 51 |
+
this.nextSaveTask = undefined;
|
| 52 |
+
this.isSaving = true;
|
| 53 |
+
try {
|
| 54 |
+
const result = await saveTask();
|
| 55 |
+
|
| 56 |
+
// Resolve only the promises that were pending when this save started
|
| 57 |
+
resolversForThisSave.forEach(({
|
| 58 |
+
resolve
|
| 59 |
+
}) => resolve(result));
|
| 60 |
+
} catch (error) {
|
| 61 |
+
// Reject only the promises that were pending when this save started
|
| 62 |
+
resolversForThisSave.forEach(({
|
| 63 |
+
reject
|
| 64 |
+
}) => reject(error));
|
| 65 |
+
} finally {
|
| 66 |
+
this.isSaving = false;
|
| 67 |
+
|
| 68 |
+
// If new saves were requested during this save, schedule another
|
| 69 |
+
if (this.pendingResolvers.length > 0) {
|
| 70 |
+
this.scheduleSave();
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
[Symbol.dispose]() {
|
| 75 |
+
if (this.saveTimeout) {
|
| 76 |
+
clearTimeout(this.saveTimeout);
|
| 77 |
+
this.saveTimeout = undefined;
|
| 78 |
+
}
|
| 79 |
+
this.pendingResolvers = [];
|
| 80 |
+
this.nextSaveTask = undefined;
|
| 81 |
+
this.isSaving = false;
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
export { SaveScheduler as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/catalogLoader.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from 'path';
|
| 2 |
+
import compile from 'icu-minify/compile';
|
| 3 |
+
import { getFormatExtension, resolveCodec } from './format/index.js';
|
| 4 |
+
import { setNestedProperty } from './utils.js';
|
| 5 |
+
|
| 6 |
+
// The module scope is safe for some caching, but Next.js can
|
| 7 |
+
// create multiple loader instances so don't expect a singleton.
|
| 8 |
+
let cachedCodec = null;
|
| 9 |
+
let hasWarnedAboutToJSONString = false;
|
| 10 |
+
const messageCacheByCatalog = new Map();
|
| 11 |
+
function getMessageCache(catalogId) {
|
| 12 |
+
let cache = messageCacheByCatalog.get(catalogId);
|
| 13 |
+
if (!cache) {
|
| 14 |
+
cache = new Map();
|
| 15 |
+
messageCacheByCatalog.set(catalogId, cache);
|
| 16 |
+
}
|
| 17 |
+
return cache;
|
| 18 |
+
}
|
| 19 |
+
async function getCodec(options, projectRoot) {
|
| 20 |
+
if (!cachedCodec) {
|
| 21 |
+
cachedCodec = await resolveCodec(options.messages.format, projectRoot);
|
| 22 |
+
}
|
| 23 |
+
return cachedCodec;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
/**
|
| 27 |
+
* Parses and optimizes catalog files.
|
| 28 |
+
*
|
| 29 |
+
* Note that if we use a dynamic import like `import(`${locale}.json`)`, then
|
| 30 |
+
* the loader will optimistically run for all candidates in this folder (both
|
| 31 |
+
* during dev as well as at build time).
|
| 32 |
+
*/
|
| 33 |
+
function catalogLoader(source) {
|
| 34 |
+
const options = this.getOptions();
|
| 35 |
+
const callback = this.async();
|
| 36 |
+
const extension = getFormatExtension(options.messages.format);
|
| 37 |
+
getCodec(options, this.rootContext).then(codec => {
|
| 38 |
+
warnIfToJSONStringProvided(codec);
|
| 39 |
+
const locale = path.basename(this.resourcePath, extension);
|
| 40 |
+
const decoded = codec.decode(source, {
|
| 41 |
+
locale,
|
| 42 |
+
sourceLocale: options.messages.sourceLocale
|
| 43 |
+
});
|
| 44 |
+
let messages;
|
| 45 |
+
if (options.messages.precompile) {
|
| 46 |
+
const cache = getMessageCache(this.resourcePath);
|
| 47 |
+
messages = precompileMessages(decoded, cache);
|
| 48 |
+
} else {
|
| 49 |
+
messages = buildMessages(decoded);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// https://v8.dev/blog/cost-of-javascript-2019#json
|
| 53 |
+
const result = `export default JSON.parse(${JSON.stringify(JSON.stringify(messages))});`;
|
| 54 |
+
callback(null, result);
|
| 55 |
+
}).catch(callback);
|
| 56 |
+
}
|
| 57 |
+
function warnIfToJSONStringProvided(codec) {
|
| 58 |
+
if (hasWarnedAboutToJSONString || !codec.toJSONString) return;
|
| 59 |
+
hasWarnedAboutToJSONString = true;
|
| 60 |
+
console.warn('`toJSONString` is deprecated and no longer needed, please remove it.');
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* Builds a nested messages object from decoded messages, mirroring the shape
|
| 65 |
+
* produced by `precompileMessages` but keeping the raw message strings.
|
| 66 |
+
*/
|
| 67 |
+
function buildMessages(messages) {
|
| 68 |
+
const result = Object.create(null);
|
| 69 |
+
for (const message of messages) {
|
| 70 |
+
assertStringMessage(message);
|
| 71 |
+
setNestedProperty(result, message.id, message.message);
|
| 72 |
+
}
|
| 73 |
+
return result;
|
| 74 |
+
}
|
| 75 |
+
function assertStringMessage(message) {
|
| 76 |
+
const messageValue = message.message;
|
| 77 |
+
if (Array.isArray(messageValue)) {
|
| 78 |
+
throw new Error(`Message at \`${message.id}\` resolved to an array, but only strings are supported. See https://next-intl.dev/docs/usage/translations#arrays-of-messages`);
|
| 79 |
+
}
|
| 80 |
+
if (typeof messageValue === 'object') {
|
| 81 |
+
throw new Error(`Message at \`${message.id}\` resolved to \`${typeof messageValue}\`, but only strings are supported. Use a \`.\` to retrieve nested messages. See https://next-intl.dev/docs/usage/translations#structuring-messages`);
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* Recursively precompiles all ICU message strings in a messages object
|
| 87 |
+
* using icu-minify/compile for smaller runtime bundles.
|
| 88 |
+
*/
|
| 89 |
+
function precompileMessages(messages, cache) {
|
| 90 |
+
const result = Object.create(null);
|
| 91 |
+
const cacheKeysToEvict = new Set(cache.keys());
|
| 92 |
+
for (const message of messages) {
|
| 93 |
+
cacheKeysToEvict.delete(message.id);
|
| 94 |
+
assertStringMessage(message);
|
| 95 |
+
const messageValue = message.message;
|
| 96 |
+
const cachedEntry = cache.get(message.id);
|
| 97 |
+
const hasCacheMatch = cachedEntry?.messageValue === messageValue;
|
| 98 |
+
let compiledMessage;
|
| 99 |
+
if (hasCacheMatch) {
|
| 100 |
+
compiledMessage = cachedEntry.compiledMessage;
|
| 101 |
+
} else {
|
| 102 |
+
compiledMessage = compile(messageValue);
|
| 103 |
+
cache.set(message.id, {
|
| 104 |
+
compiledMessage,
|
| 105 |
+
messageValue
|
| 106 |
+
});
|
| 107 |
+
}
|
| 108 |
+
setNestedProperty(result, message.id, compiledMessage);
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Evict unused cache entries
|
| 112 |
+
for (const cachedId of cacheKeysToEvict) {
|
| 113 |
+
cache.delete(cachedId);
|
| 114 |
+
}
|
| 115 |
+
return result;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
export { catalogLoader as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractMessages.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { warn } from '../plugin/utils.js';
|
| 2 |
+
import ExtractionCompiler from './ExtractionCompiler.js';
|
| 3 |
+
import MessageExtractor from './extractor/MessageExtractor.js';
|
| 4 |
+
import normalizeExtractorConfig from './normalizeExtractorConfig.js';
|
| 5 |
+
import { hasLocalesToExtract, getDefaultProjectRoot } from './utils.js';
|
| 6 |
+
|
| 7 |
+
async function extractMessages(params) {
|
| 8 |
+
const config = normalizeExtractorConfig(params);
|
| 9 |
+
if (!hasLocalesToExtract(config)) {
|
| 10 |
+
warn('`messages.locales` is empty, so no messages were updated.');
|
| 11 |
+
return;
|
| 12 |
+
}
|
| 13 |
+
const compiler = new ExtractionCompiler(config, {
|
| 14 |
+
extractor: new MessageExtractor({
|
| 15 |
+
isDevelopment: false,
|
| 16 |
+
projectRoot: getDefaultProjectRoot()
|
| 17 |
+
})
|
| 18 |
+
});
|
| 19 |
+
await compiler.extractAll();
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export { extractMessages as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractionLoader.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import MessageExtractor from './extractor/MessageExtractor.js';
|
| 2 |
+
|
| 3 |
+
// Module-level extractor instance for transformation caching.
|
| 4 |
+
// Note: Next.js/Turbopack may create multiple loader instances, but each
|
| 5 |
+
// only handles file transformation. The ExtractionCompiler (which manages
|
| 6 |
+
// catalogs) is initialized separately in createNextIntlPlugin.
|
| 7 |
+
let extractor;
|
| 8 |
+
function extractionLoader(source) {
|
| 9 |
+
const callback = this.async();
|
| 10 |
+
const projectRoot = this.rootContext;
|
| 11 |
+
|
| 12 |
+
// Avoid rollup's `replace` plugin to compile this away
|
| 13 |
+
const isDevelopment = process.env['NODE_ENV'.trim()] === 'development';
|
| 14 |
+
if (!extractor) {
|
| 15 |
+
extractor = new MessageExtractor({
|
| 16 |
+
isDevelopment,
|
| 17 |
+
projectRoot,
|
| 18 |
+
sourceMap: this.sourceMap
|
| 19 |
+
});
|
| 20 |
+
}
|
| 21 |
+
extractor.extract(this.resourcePath, source).then(result => {
|
| 22 |
+
// Webpack loaders conventionally pass source maps as objects (e.g.
|
| 23 |
+
// `next-swc-loader` calls `JSON.stringify` on its input source map)
|
| 24 |
+
callback(null, result.code, result.map ? JSON.parse(result.map) : undefined);
|
| 25 |
+
}).catch(callback);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
export { extractionLoader as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractor/LRUCache.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class LRUCache {
|
| 2 |
+
constructor(maxSize) {
|
| 3 |
+
this.maxSize = maxSize;
|
| 4 |
+
this.cache = new Map();
|
| 5 |
+
}
|
| 6 |
+
set(key, value) {
|
| 7 |
+
const isNewKey = !this.cache.has(key);
|
| 8 |
+
if (isNewKey && this.cache.size >= this.maxSize) {
|
| 9 |
+
const lruKey = this.cache.keys().next().value;
|
| 10 |
+
if (lruKey !== undefined) {
|
| 11 |
+
this.cache.delete(lruKey);
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
this.cache.set(key, {
|
| 15 |
+
key,
|
| 16 |
+
value
|
| 17 |
+
});
|
| 18 |
+
}
|
| 19 |
+
get(key) {
|
| 20 |
+
const item = this.cache.get(key);
|
| 21 |
+
if (item) {
|
| 22 |
+
this.cache.delete(key);
|
| 23 |
+
this.cache.set(key, item);
|
| 24 |
+
return item.value;
|
| 25 |
+
}
|
| 26 |
+
return undefined;
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
export { LRUCache as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/extractor/MessageExtractor.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createRequire } from 'module';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
import { transform } from '@swc/core';
|
| 4 |
+
import { getDefaultProjectRoot, normalizePathToPosix } from '../utils.js';
|
| 5 |
+
import LRUCache from './LRUCache.js';
|
| 6 |
+
|
| 7 |
+
const require$1 = createRequire(import.meta.url);
|
| 8 |
+
class MessageExtractor {
|
| 9 |
+
compileCache = new LRUCache(750);
|
| 10 |
+
constructor(opts) {
|
| 11 |
+
this.isDevelopment = opts.isDevelopment ?? false;
|
| 12 |
+
this.projectRoot = opts.projectRoot ?? getDefaultProjectRoot();
|
| 13 |
+
this.sourceMap = opts.sourceMap ?? false;
|
| 14 |
+
}
|
| 15 |
+
async extract(absoluteFilePath, source) {
|
| 16 |
+
const cacheKey = [source, absoluteFilePath].join('!');
|
| 17 |
+
const cached = this.compileCache.get(cacheKey);
|
| 18 |
+
if (cached) return cached;
|
| 19 |
+
|
| 20 |
+
// Shortcut parsing if hook is not used. The Turbopack integration already
|
| 21 |
+
// pre-filters this, but for webpack this feature doesn't exist, so we need
|
| 22 |
+
// to do it here.
|
| 23 |
+
if (!source.includes('useExtracted') && !source.includes('getExtracted')) {
|
| 24 |
+
return {
|
| 25 |
+
messages: [],
|
| 26 |
+
code: source
|
| 27 |
+
};
|
| 28 |
+
}
|
| 29 |
+
const filePath = normalizePathToPosix(path.relative(this.projectRoot, absoluteFilePath));
|
| 30 |
+
const result = await transform(source, {
|
| 31 |
+
jsc: {
|
| 32 |
+
target: 'esnext',
|
| 33 |
+
parser: {
|
| 34 |
+
syntax: 'typescript',
|
| 35 |
+
tsx: true,
|
| 36 |
+
decorators: true
|
| 37 |
+
},
|
| 38 |
+
experimental: {
|
| 39 |
+
cacheRoot: 'node_modules/.cache/swc',
|
| 40 |
+
disableBuiltinTransformsForInternalTesting: true,
|
| 41 |
+
disableAllLints: true,
|
| 42 |
+
plugins: [[require$1.resolve('next-intl-swc-plugin-extractor'), {
|
| 43 |
+
isDevelopment: this.isDevelopment,
|
| 44 |
+
filePath
|
| 45 |
+
}]]
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
sourceMaps: this.sourceMap,
|
| 49 |
+
// Is converted into a relative path by Next.js when
|
| 50 |
+
// the final source map is generated
|
| 51 |
+
sourceFileName: absoluteFilePath,
|
| 52 |
+
filename: filePath
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
// TODO: Improve the typing of @swc/core
|
| 56 |
+
const output = result.output;
|
| 57 |
+
// The plugin emits a tagged union of extracted messages and
|
| 58 |
+
// `useTranslations` usages; this extractor only consumes the former.
|
| 59 |
+
const messages = JSON.parse(JSON.parse(output).results).filter(item => item.type === 'extracted').map(item => ({
|
| 60 |
+
id: item.id,
|
| 61 |
+
message: item.message,
|
| 62 |
+
description: item.description,
|
| 63 |
+
reference: item.reference
|
| 64 |
+
}));
|
| 65 |
+
const extractionResult = {
|
| 66 |
+
code: result.code,
|
| 67 |
+
map: result.map,
|
| 68 |
+
messages
|
| 69 |
+
};
|
| 70 |
+
this.compileCache.set(cacheKey, extractionResult);
|
| 71 |
+
return extractionResult;
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
export { MessageExtractor as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/format/ExtractorCodec.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Derived from `@eloqnt/config`, so next-intl and eloqnt pass identical
|
| 2 |
+
// options to codecs.
|
| 3 |
+
|
| 4 |
+
function defineCodec(factory) {
|
| 5 |
+
return factory;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export { defineCodec };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/format/codecs/BuiltInPoCodec.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import createPoCodec from '@eloqnt/format-po';
|
| 2 |
+
import { defineCodec } from '../ExtractorCodec.js';
|
| 3 |
+
|
| 4 |
+
// `@eloqnt/format-po`, guarded so catalogs in the previous key-based layout
|
| 5 |
+
// fail loudly. Custom codecs referencing the package directly opt out.
|
| 6 |
+
var BuiltInPoCodec = defineCodec(() => {
|
| 7 |
+
const codec = createPoCodec();
|
| 8 |
+
function assertMigrated(content) {
|
| 9 |
+
if (content.includes('X-Crowdin-SourceKey: msgstr')) {
|
| 10 |
+
throw new Error('This catalog uses the previous PO layout of next-intl, which stored message keys in `msgid`. The PO format now follows the gettext convention: the source text in `msgid` and the message key in `msgctxt`. Please migrate your catalogs, or use a custom codec to keep the previous layout: https://github.com/amannn/next-intl/pull/2393');
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
return {
|
| 14 |
+
...codec,
|
| 15 |
+
decode(content, context) {
|
| 16 |
+
assertMigrated(content);
|
| 17 |
+
return codec.decode(content, context);
|
| 18 |
+
}
|
| 19 |
+
};
|
| 20 |
+
});
|
| 21 |
+
|
| 22 |
+
export { BuiltInPoCodec as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/format/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createRequire } from 'module';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
import { pathToFileURL } from 'url';
|
| 4 |
+
import { throwError } from '../../plugin/utils.js';
|
| 5 |
+
|
| 6 |
+
const formats = {
|
| 7 |
+
json: {
|
| 8 |
+
codec: () => import('@eloqnt/format-json'),
|
| 9 |
+
extension: '.json'
|
| 10 |
+
},
|
| 11 |
+
po: {
|
| 12 |
+
codec: () => import('./codecs/BuiltInPoCodec.js'),
|
| 13 |
+
extension: '.po'
|
| 14 |
+
}
|
| 15 |
+
};
|
| 16 |
+
function isBuiltInFormat(format) {
|
| 17 |
+
return typeof format === 'string' && format in formats;
|
| 18 |
+
}
|
| 19 |
+
function getFormatExtension(format) {
|
| 20 |
+
if (isBuiltInFormat(format)) {
|
| 21 |
+
return formats[format].extension;
|
| 22 |
+
} else {
|
| 23 |
+
return format.extension;
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
async function resolveCodec(format, projectRoot) {
|
| 27 |
+
if (isBuiltInFormat(format)) {
|
| 28 |
+
const factory = (await formats[format].codec()).default;
|
| 29 |
+
return factory();
|
| 30 |
+
} else {
|
| 31 |
+
// A codec that starts with `.` or a filesystem root is a file path;
|
| 32 |
+
// everything else reads as a package specifier, mirroring how imports
|
| 33 |
+
// distinguish the two. Packages resolve from the project's dependencies.
|
| 34 |
+
const isPath = format.codec.startsWith('.') || path.isAbsolute(format.codec);
|
| 35 |
+
let resolvedPath;
|
| 36 |
+
if (isPath) {
|
| 37 |
+
resolvedPath = path.resolve(projectRoot, format.codec);
|
| 38 |
+
} else {
|
| 39 |
+
try {
|
| 40 |
+
resolvedPath = createRequire(path.join(projectRoot, 'noop.js')).resolve(format.codec);
|
| 41 |
+
} catch (error) {
|
| 42 |
+
throwError(`Could not resolve the codec package "${format.codec}". Is it installed?\n${error}`);
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
let module;
|
| 46 |
+
try {
|
| 47 |
+
module = await import(pathToFileURL(resolvedPath).href);
|
| 48 |
+
} catch (error) {
|
| 49 |
+
throwError(`Could not load codec from "${resolvedPath}".\n${error}`);
|
| 50 |
+
}
|
| 51 |
+
const factory = module.default;
|
| 52 |
+
if (!factory || typeof factory !== 'function') {
|
| 53 |
+
throwError(`Codec at "${resolvedPath}" must have a default export returned from \`defineCodec\`.`);
|
| 54 |
+
}
|
| 55 |
+
return factory();
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
export { formats as default, getFormatExtension, resolveCodec };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/normalizeExtractorConfig.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { throwError, warn } from '../plugin/utils.js';
|
| 2 |
+
|
| 3 |
+
function stripTrailingSlash(dirPath) {
|
| 4 |
+
if (dirPath.endsWith('/')) {
|
| 5 |
+
return dirPath.slice(0, -1);
|
| 6 |
+
} else {
|
| 7 |
+
return dirPath;
|
| 8 |
+
}
|
| 9 |
+
}
|
| 10 |
+
function normalizeMessagesCatalogPaths(messagesPath) {
|
| 11 |
+
const rawPaths = Array.isArray(messagesPath) ? messagesPath : [messagesPath];
|
| 12 |
+
return rawPaths.map(dirPath => stripTrailingSlash(String(dirPath).trim())).filter(dirPath => dirPath.length > 0);
|
| 13 |
+
}
|
| 14 |
+
function normalizeExtractorConfig(input) {
|
| 15 |
+
if (input.messages == null) {
|
| 16 |
+
throwError('`messages` is required when extracting messages.');
|
| 17 |
+
}
|
| 18 |
+
const extract = input.extract;
|
| 19 |
+
let extractPath;
|
| 20 |
+
let sourceLocale;
|
| 21 |
+
if (extract !== undefined && extract !== true) {
|
| 22 |
+
if (extract.sourceLocale) {
|
| 23 |
+
warn('`extract.sourceLocale` is deprecated in favor of `messages.sourceLocale`.');
|
| 24 |
+
sourceLocale = extract.sourceLocale;
|
| 25 |
+
}
|
| 26 |
+
if (extract.path) {
|
| 27 |
+
extractPath = stripTrailingSlash(extract.path);
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
const locales = input.messages.locales;
|
| 31 |
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
| 32 |
+
if (!locales) {
|
| 33 |
+
throwError('`messages.locales` is required when extracting messages.');
|
| 34 |
+
}
|
| 35 |
+
if (input.messages.sourceLocale) {
|
| 36 |
+
sourceLocale = input.messages.sourceLocale;
|
| 37 |
+
}
|
| 38 |
+
if (!sourceLocale) {
|
| 39 |
+
throwError('`messages.sourceLocale` is required when extracting messages.');
|
| 40 |
+
}
|
| 41 |
+
const srcPath = input.srcPath;
|
| 42 |
+
if (srcPath == null) {
|
| 43 |
+
throwError('`srcPath` is required when extracting messages.');
|
| 44 |
+
}
|
| 45 |
+
const pathIsArray = Array.isArray(input.messages.path);
|
| 46 |
+
const messagesPath = normalizeMessagesCatalogPaths(input.messages.path);
|
| 47 |
+
if (messagesPath.length === 0) {
|
| 48 |
+
throwError('`messages.path` must not be empty.');
|
| 49 |
+
}
|
| 50 |
+
if (extractPath == null) {
|
| 51 |
+
if (pathIsArray) {
|
| 52 |
+
throwError('When `messages.path` is an array, `extract.path` is required to select the writable catalog directory.');
|
| 53 |
+
}
|
| 54 |
+
extractPath = messagesPath[0];
|
| 55 |
+
}
|
| 56 |
+
return {
|
| 57 |
+
extract: {
|
| 58 |
+
locales,
|
| 59 |
+
path: extractPath,
|
| 60 |
+
sourceLocale,
|
| 61 |
+
srcPath
|
| 62 |
+
},
|
| 63 |
+
messages: {
|
| 64 |
+
format: input.messages.format,
|
| 65 |
+
path: messagesPath
|
| 66 |
+
}
|
| 67 |
+
};
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
export { normalizeExtractorConfig as default, normalizeMessagesCatalogPaths };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/source/SourceFileFilter.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from 'path';
|
| 2 |
+
|
| 3 |
+
class SourceFileFilter {
|
| 4 |
+
static EXTENSIONS = ['ts', 'tsx', 'js', 'jsx'];
|
| 5 |
+
|
| 6 |
+
// Will not be entered, except if explicitly asked for
|
| 7 |
+
// TODO: At some point we should infer these from .gitignore
|
| 8 |
+
static IGNORED_DIRECTORIES = ['node_modules', '.next', '.git'];
|
| 9 |
+
static isSourceFile(filePath) {
|
| 10 |
+
const ext = path.extname(filePath);
|
| 11 |
+
return SourceFileFilter.EXTENSIONS.map(cur => '.' + cur).includes(ext);
|
| 12 |
+
}
|
| 13 |
+
static shouldEnterDirectory(dirPath, srcPaths) {
|
| 14 |
+
const dirName = path.basename(dirPath);
|
| 15 |
+
if (SourceFileFilter.IGNORED_DIRECTORIES.includes(dirName)) {
|
| 16 |
+
return SourceFileFilter.isIgnoredDirectoryExplicitlyIncluded(dirPath, srcPaths);
|
| 17 |
+
}
|
| 18 |
+
return true;
|
| 19 |
+
}
|
| 20 |
+
static isIgnoredDirectoryExplicitlyIncluded(ignoredDirPath, srcPaths) {
|
| 21 |
+
return srcPaths.some(srcPath => SourceFileFilter.isWithinPath(srcPath, ignoredDirPath));
|
| 22 |
+
}
|
| 23 |
+
static isWithinPath(targetPath, basePath) {
|
| 24 |
+
const relativePath = path.relative(basePath, targetPath);
|
| 25 |
+
return relativePath === '' || !relativePath.startsWith('..');
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
export { SourceFileFilter as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/source/SourceFileScanner.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'fs/promises';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
import SourceFileFilter from './SourceFileFilter.js';
|
| 4 |
+
|
| 5 |
+
class SourceFileScanner {
|
| 6 |
+
static async walkSourceFiles(dir, srcPaths, acc = []) {
|
| 7 |
+
const entries = await fs.readdir(dir, {
|
| 8 |
+
withFileTypes: true
|
| 9 |
+
});
|
| 10 |
+
for (const entry of entries) {
|
| 11 |
+
const entryPath = path.join(dir, entry.name);
|
| 12 |
+
if (entry.isDirectory()) {
|
| 13 |
+
if (!SourceFileFilter.shouldEnterDirectory(entryPath, srcPaths)) {
|
| 14 |
+
continue;
|
| 15 |
+
}
|
| 16 |
+
await SourceFileScanner.walkSourceFiles(entryPath, srcPaths, acc);
|
| 17 |
+
} else {
|
| 18 |
+
if (SourceFileFilter.isSourceFile(entry.name)) {
|
| 19 |
+
acc.push(entryPath);
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
}
|
| 23 |
+
return acc;
|
| 24 |
+
}
|
| 25 |
+
static async getSourceFiles(srcPaths) {
|
| 26 |
+
const files = (await Promise.all(srcPaths.map(srcPath => SourceFileScanner.walkSourceFiles(srcPath, srcPaths)))).flat();
|
| 27 |
+
return new Set(files);
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
export { SourceFileScanner as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/source/SourceFileWatcher.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'fs/promises';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
import { subscribe } from '@parcel/watcher';
|
| 4 |
+
import SourceFileFilter from './SourceFileFilter.js';
|
| 5 |
+
import SourceFileScanner from './SourceFileScanner.js';
|
| 6 |
+
|
| 7 |
+
class SourceFileWatcher {
|
| 8 |
+
subscriptions = [];
|
| 9 |
+
constructor(roots, onChange) {
|
| 10 |
+
this.roots = roots;
|
| 11 |
+
this.onChange = onChange;
|
| 12 |
+
}
|
| 13 |
+
async start() {
|
| 14 |
+
if (this.subscriptions.length > 0) {
|
| 15 |
+
return;
|
| 16 |
+
}
|
| 17 |
+
const ignore = SourceFileFilter.IGNORED_DIRECTORIES.map(dir => `**/${dir}/**`);
|
| 18 |
+
for (const root of this.roots) {
|
| 19 |
+
const sub = await subscribe(root, async (err, events) => {
|
| 20 |
+
if (err) {
|
| 21 |
+
console.error(err);
|
| 22 |
+
return;
|
| 23 |
+
}
|
| 24 |
+
const filtered = await this.normalizeEvents(events);
|
| 25 |
+
if (filtered.length > 0) {
|
| 26 |
+
await this.onChange(filtered);
|
| 27 |
+
}
|
| 28 |
+
}, {
|
| 29 |
+
ignore
|
| 30 |
+
});
|
| 31 |
+
this.subscriptions.push(sub);
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
async normalizeEvents(events) {
|
| 35 |
+
const directoryCreatePaths = [];
|
| 36 |
+
const otherEvents = [];
|
| 37 |
+
|
| 38 |
+
// We need to expand directory creates because during rename operations,
|
| 39 |
+
// @parcel/watcher emits a directory create event but may not emit individual
|
| 40 |
+
// file events for the moved files
|
| 41 |
+
await Promise.all(events.map(async event => {
|
| 42 |
+
if (event.type === 'create') {
|
| 43 |
+
try {
|
| 44 |
+
const stats = await fs.stat(event.path);
|
| 45 |
+
if (stats.isDirectory()) {
|
| 46 |
+
directoryCreatePaths.push(event.path);
|
| 47 |
+
return;
|
| 48 |
+
}
|
| 49 |
+
} catch {
|
| 50 |
+
// Path doesn't exist or is inaccessible, treat as file
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
otherEvents.push(event);
|
| 54 |
+
}));
|
| 55 |
+
|
| 56 |
+
// Expand directory create events to find source files inside
|
| 57 |
+
let expandedCreateEvents = [];
|
| 58 |
+
if (directoryCreatePaths.length > 0) {
|
| 59 |
+
try {
|
| 60 |
+
const sourceFiles = await SourceFileScanner.getSourceFiles(directoryCreatePaths);
|
| 61 |
+
expandedCreateEvents = Array.from(sourceFiles).map(filePath => ({
|
| 62 |
+
type: 'create',
|
| 63 |
+
path: filePath
|
| 64 |
+
}));
|
| 65 |
+
} catch {
|
| 66 |
+
// Directories might have been deleted or are inaccessible
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// Combine original events with expanded directory creates.
|
| 71 |
+
// Deduplicate by path to avoid processing the same file twice
|
| 72 |
+
// in case @parcel/watcher also emitted individual file events.
|
| 73 |
+
const allEvents = [...otherEvents, ...expandedCreateEvents];
|
| 74 |
+
const seenPaths = new Set();
|
| 75 |
+
const deduplicated = [];
|
| 76 |
+
for (const event of allEvents) {
|
| 77 |
+
const key = `${event.type}:${event.path}`;
|
| 78 |
+
if (!seenPaths.has(key)) {
|
| 79 |
+
seenPaths.add(key);
|
| 80 |
+
deduplicated.push(event);
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
return deduplicated.filter(event => {
|
| 84 |
+
// Keep all delete events (might be deleted directories that no longer exist)
|
| 85 |
+
if (event.type === 'delete') {
|
| 86 |
+
return true;
|
| 87 |
+
}
|
| 88 |
+
// Keep source files
|
| 89 |
+
return SourceFileFilter.isSourceFile(event.path);
|
| 90 |
+
});
|
| 91 |
+
}
|
| 92 |
+
async expandDirectoryDeleteEvents(events, prevKnownFiles) {
|
| 93 |
+
const expanded = [];
|
| 94 |
+
for (const event of events) {
|
| 95 |
+
if (event.type === 'delete' && !SourceFileFilter.isSourceFile(event.path)) {
|
| 96 |
+
const dirPath = path.resolve(event.path);
|
| 97 |
+
const filesInDirectory = [];
|
| 98 |
+
for (const filePath of prevKnownFiles) {
|
| 99 |
+
if (SourceFileFilter.isWithinPath(filePath, dirPath)) {
|
| 100 |
+
filesInDirectory.push(filePath);
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// If we found files within this path, it was a directory
|
| 105 |
+
if (filesInDirectory.length > 0) {
|
| 106 |
+
for (const filePath of filesInDirectory) {
|
| 107 |
+
expanded.push({
|
| 108 |
+
type: 'delete',
|
| 109 |
+
path: filePath
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
} else {
|
| 113 |
+
// Not a directory or no files in it, pass through as-is
|
| 114 |
+
expanded.push(event);
|
| 115 |
+
}
|
| 116 |
+
} else {
|
| 117 |
+
// Pass through as-is
|
| 118 |
+
expanded.push(event);
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
return expanded;
|
| 122 |
+
}
|
| 123 |
+
async stop() {
|
| 124 |
+
await Promise.all(this.subscriptions.map(sub => sub.unsubscribe()));
|
| 125 |
+
this.subscriptions = [];
|
| 126 |
+
}
|
| 127 |
+
[Symbol.dispose]() {
|
| 128 |
+
void this.stop();
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
export { SourceFileWatcher as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/extractor/utils.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from 'path';
|
| 2 |
+
import { warn } from '../plugin/utils.js';
|
| 3 |
+
|
| 4 |
+
function normalizePathToPosix(filePath) {
|
| 5 |
+
// `path.relative` uses OS-specific separators. For stable `.po` references we
|
| 6 |
+
// always use POSIX separators, regardless of the OS that ran extraction.
|
| 7 |
+
return path.posix.normalize(filePath.split(path.win32.sep).join(path.posix.sep));
|
| 8 |
+
}
|
| 9 |
+
const FORBIDDEN_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
| 10 |
+
function isForbiddenObjectKey(key) {
|
| 11 |
+
return FORBIDDEN_OBJECT_KEYS.has(key);
|
| 12 |
+
}
|
| 13 |
+
function hasLocalesToExtract(config) {
|
| 14 |
+
const {
|
| 15 |
+
locales
|
| 16 |
+
} = config.extract;
|
| 17 |
+
return locales === 'infer' || locales.length > 0;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
// Essentialls lodash/set, but we avoid this dependency
|
| 21 |
+
function setNestedProperty(obj, keyPath, value) {
|
| 22 |
+
const keys = keyPath.split('.');
|
| 23 |
+
for (const key of keys) {
|
| 24 |
+
if (isForbiddenObjectKey(key)) {
|
| 25 |
+
throw new Error(`Invalid message id segment: ${key}`);
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
let current = obj;
|
| 29 |
+
for (let i = 0; i < keys.length - 1; i++) {
|
| 30 |
+
const key = keys[i];
|
| 31 |
+
if (!Object.prototype.hasOwnProperty.call(current, key) || typeof current[key] !== 'object' || current[key] === null) {
|
| 32 |
+
current[key] = Object.create(null);
|
| 33 |
+
}
|
| 34 |
+
current = current[key];
|
| 35 |
+
}
|
| 36 |
+
current[keys[keys.length - 1]] = value;
|
| 37 |
+
}
|
| 38 |
+
function warnAboutMissingReferences(messages) {
|
| 39 |
+
const warnedIds = new Set();
|
| 40 |
+
for (const message of messages) {
|
| 41 |
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
| 42 |
+
if (message.references[0] == null && !warnedIds.has(message.id)) {
|
| 43 |
+
warnedIds.add(message.id);
|
| 44 |
+
warn(`Missing file reference for extracted message: ${message.id}`);
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
function localeCompare(a, b) {
|
| 49 |
+
return a.localeCompare(b, 'en');
|
| 50 |
+
}
|
| 51 |
+
function compareReferences(refA, refB) {
|
| 52 |
+
const pathCompare = localeCompare(refA.path, refB.path);
|
| 53 |
+
if (pathCompare !== 0) return pathCompare;
|
| 54 |
+
return (refA.line ?? 0) - (refB.line ?? 0) || (refA.column ?? 0) - (refB.column ?? 0);
|
| 55 |
+
}
|
| 56 |
+
function getDefaultProjectRoot() {
|
| 57 |
+
return process.cwd();
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
export { compareReferences, getDefaultProjectRoot, hasLocalesToExtract, isForbiddenObjectKey, localeCompare, normalizePathToPosix, setNestedProperty, warnAboutMissingReferences };
|
e-commerce/node_modules/next-intl/dist/esm/development/index.react-client.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { useFormatter, useTranslations } from './react-client/index.js';
|
| 2 |
+
export { default as NextIntlClientProvider } from './shared/NextIntlClientProvider.js';
|
| 3 |
+
export { IntlProvider, _useExtracted as useExtracted, useLocale, useMessages, useNow, useTimeZone } from 'use-intl/react';
|
| 4 |
+
export * from 'use-intl/core';
|
e-commerce/node_modules/next-intl/dist/esm/development/index.react-server.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { default as useLocale } from './react-server/useLocale.js';
|
| 2 |
+
export { default as useTranslations } from './react-server/useTranslations.js';
|
| 3 |
+
export { default as useFormatter } from './react-server/useFormatter.js';
|
| 4 |
+
export { default as useNow } from './react-server/useNow.js';
|
| 5 |
+
export { default as useTimeZone } from './react-server/useTimeZone.js';
|
| 6 |
+
export { default as useMessages } from './react-server/useMessages.js';
|
| 7 |
+
export { default as NextIntlClientProvider } from './react-server/NextIntlClientProviderServer.js';
|
| 8 |
+
export { default as useExtracted } from './react-server/useExtracted.js';
|
| 9 |
+
export * from 'use-intl/core';
|
e-commerce/node_modules/next-intl/dist/esm/development/middleware.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export { default } from './middleware/middleware.js';
|
e-commerce/node_modules/next-intl/dist/esm/development/middleware/getAlternateLinksHeaderValue.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { normalizeTrailingSlash } from '../shared/utils.js';
|
| 2 |
+
import { getHost, getNormalizedPathname, getLocalePrefixes, isLocaleSupportedOnDomain, applyBasePath, formatTemplatePathname } from './utils.js';
|
| 3 |
+
|
| 4 |
+
/**
|
| 5 |
+
* See https://developers.google.com/search/docs/specialty/international/localized-versions
|
| 6 |
+
*/
|
| 7 |
+
function getAlternateLinksHeaderValue({
|
| 8 |
+
internalTemplateName,
|
| 9 |
+
localizedPathnames,
|
| 10 |
+
request,
|
| 11 |
+
resolvedLocale,
|
| 12 |
+
routing
|
| 13 |
+
}) {
|
| 14 |
+
const normalizedUrl = request.nextUrl.clone();
|
| 15 |
+
const host = getHost(request.headers);
|
| 16 |
+
if (host) {
|
| 17 |
+
normalizedUrl.port = '';
|
| 18 |
+
normalizedUrl.host = host;
|
| 19 |
+
}
|
| 20 |
+
normalizedUrl.protocol = request.headers.get('x-forwarded-proto') ?? normalizedUrl.protocol;
|
| 21 |
+
normalizedUrl.pathname = getNormalizedPathname(normalizedUrl.pathname, routing.locales, routing.localePrefix);
|
| 22 |
+
function getAlternateEntry(url, locale) {
|
| 23 |
+
url.pathname = normalizeTrailingSlash(url.pathname);
|
| 24 |
+
if (request.nextUrl.basePath) {
|
| 25 |
+
url = new URL(url);
|
| 26 |
+
url.pathname = applyBasePath(url.pathname, request.nextUrl.basePath);
|
| 27 |
+
}
|
| 28 |
+
return `<${url.toString()}>; rel="alternate"; hreflang="${locale}"`;
|
| 29 |
+
}
|
| 30 |
+
function getLocalizedPathname(pathname, locale) {
|
| 31 |
+
if (localizedPathnames && typeof localizedPathnames === 'object') {
|
| 32 |
+
const sourceTemplate = localizedPathnames[resolvedLocale];
|
| 33 |
+
return formatTemplatePathname(pathname, sourceTemplate ?? internalTemplateName, localizedPathnames[locale] ?? internalTemplateName);
|
| 34 |
+
} else {
|
| 35 |
+
return pathname;
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
const links = getLocalePrefixes(routing.locales, routing.localePrefix, false).flatMap(([locale, prefix]) => {
|
| 39 |
+
function prefixPathname(pathname) {
|
| 40 |
+
if (pathname === '/') {
|
| 41 |
+
return prefix;
|
| 42 |
+
} else {
|
| 43 |
+
return prefix + pathname;
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
let url;
|
| 47 |
+
if (routing.domains) {
|
| 48 |
+
const domainConfigs = routing.domains.filter(cur => isLocaleSupportedOnDomain(locale, cur));
|
| 49 |
+
return domainConfigs.map(domainConfig => {
|
| 50 |
+
url = new URL(normalizedUrl);
|
| 51 |
+
url.port = '';
|
| 52 |
+
url.host = domainConfig.domain;
|
| 53 |
+
|
| 54 |
+
// Important: Use `normalizedUrl` here, as `url` potentially uses
|
| 55 |
+
// a `basePath` that automatically gets applied to the pathname
|
| 56 |
+
url.pathname = getLocalizedPathname(normalizedUrl.pathname, locale);
|
| 57 |
+
|
| 58 |
+
// Use domain-specific localePrefix mode if available
|
| 59 |
+
const domainMode = domainConfig.localePrefix || routing.localePrefix.mode;
|
| 60 |
+
if (locale !== domainConfig.defaultLocale || domainMode === 'always') {
|
| 61 |
+
url.pathname = prefixPathname(url.pathname);
|
| 62 |
+
}
|
| 63 |
+
return getAlternateEntry(url, locale);
|
| 64 |
+
});
|
| 65 |
+
} else {
|
| 66 |
+
let pathname;
|
| 67 |
+
if (localizedPathnames && typeof localizedPathnames === 'object') {
|
| 68 |
+
pathname = getLocalizedPathname(normalizedUrl.pathname, locale);
|
| 69 |
+
} else {
|
| 70 |
+
pathname = normalizedUrl.pathname;
|
| 71 |
+
}
|
| 72 |
+
if (locale !== routing.defaultLocale || routing.localePrefix.mode === 'always') {
|
| 73 |
+
pathname = prefixPathname(pathname);
|
| 74 |
+
}
|
| 75 |
+
url = new URL(pathname, normalizedUrl);
|
| 76 |
+
}
|
| 77 |
+
return getAlternateEntry(url, locale);
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
// Add x-default entry
|
| 81 |
+
const shouldAddXDefault =
|
| 82 |
+
// For domain-based routing there is no reasonable x-default
|
| 83 |
+
!routing.domains || routing.domains.length === 0;
|
| 84 |
+
if (shouldAddXDefault) {
|
| 85 |
+
const localizedPathname = getLocalizedPathname(normalizedUrl.pathname, routing.defaultLocale);
|
| 86 |
+
if (localizedPathname) {
|
| 87 |
+
const url = new URL(localizedPathname, normalizedUrl);
|
| 88 |
+
links.push(getAlternateEntry(url, 'x-default'));
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
return links.join(', ');
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
export { getAlternateLinksHeaderValue as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/middleware/middleware.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextResponse } from 'next/server';
|
| 2 |
+
import { receiveRoutingConfig } from '../routing/config.js';
|
| 3 |
+
import { HEADER_LOCALE_NAME } from '../shared/constants.js';
|
| 4 |
+
import { matchesPathname, normalizeTrailingSlash, getLocalePrefix, getLocalizedTemplate } from '../shared/utils.js';
|
| 5 |
+
import getAlternateLinksHeaderValue from './getAlternateLinksHeaderValue.js';
|
| 6 |
+
import resolveLocale from './resolveLocale.js';
|
| 7 |
+
import syncCookie from './syncCookie.js';
|
| 8 |
+
import { sanitizePathname, isLocaleSupportedOnDomain, getNormalizedPathname, getPathnameMatch, getInternalTemplate, formatTemplatePathname, formatPathname, getBestMatchingDomain, applyBasePath, getLocaleAsPrefix } from './utils.js';
|
| 9 |
+
|
| 10 |
+
function createMiddleware(routing) {
|
| 11 |
+
const resolvedRouting = receiveRoutingConfig(routing);
|
| 12 |
+
return function middleware(request) {
|
| 13 |
+
let unsafeExternalPathname;
|
| 14 |
+
try {
|
| 15 |
+
// Resolve potential foreign symbols (e.g. /ja/%E7%B4%84 → /ja/約))
|
| 16 |
+
unsafeExternalPathname = decodeURI(request.nextUrl.pathname);
|
| 17 |
+
} catch {
|
| 18 |
+
// In case an invalid pathname is encountered, forward
|
| 19 |
+
// it to Next.js which in turn responds with a 400
|
| 20 |
+
return NextResponse.next();
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
// Sanitize malicious URIs to prevent open redirect attacks due to
|
| 24 |
+
// decodeURI doesn't escape encoded backslashes ('%5C' & '%5c')
|
| 25 |
+
const externalPathname = sanitizePathname(unsafeExternalPathname);
|
| 26 |
+
const {
|
| 27 |
+
domain,
|
| 28 |
+
locale
|
| 29 |
+
} = resolveLocale(resolvedRouting, request.headers, request.cookies, externalPathname);
|
| 30 |
+
const hasMatchedDefaultLocale = domain ? domain.defaultLocale === locale : locale === resolvedRouting.defaultLocale;
|
| 31 |
+
const domainsConfig = resolvedRouting.domains?.filter(curDomain => isLocaleSupportedOnDomain(locale, curDomain)) || [];
|
| 32 |
+
const hasUnknownHost = resolvedRouting.domains != null && !domain;
|
| 33 |
+
function next(url) {
|
| 34 |
+
const urlObj = new URL(url, request.url);
|
| 35 |
+
if (request.nextUrl.basePath) {
|
| 36 |
+
urlObj.pathname = applyBasePath(urlObj.pathname, request.nextUrl.basePath);
|
| 37 |
+
}
|
| 38 |
+
const headers = new Headers(request.headers);
|
| 39 |
+
headers.set(HEADER_LOCALE_NAME, locale);
|
| 40 |
+
const isRewriteNecessary = normalizeTrailingSlash(request.nextUrl.pathname) !== normalizeTrailingSlash(urlObj.pathname);
|
| 41 |
+
if (isRewriteNecessary) {
|
| 42 |
+
return NextResponse.rewrite(urlObj, {
|
| 43 |
+
request: {
|
| 44 |
+
headers
|
| 45 |
+
}
|
| 46 |
+
});
|
| 47 |
+
} else {
|
| 48 |
+
return NextResponse.next({
|
| 49 |
+
request: {
|
| 50 |
+
headers
|
| 51 |
+
}
|
| 52 |
+
});
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
function redirect(url, redirectDomain) {
|
| 56 |
+
const urlObj = new URL(url, request.url);
|
| 57 |
+
urlObj.pathname = normalizeTrailingSlash(urlObj.pathname);
|
| 58 |
+
if (domainsConfig.length > 0 && !redirectDomain && domain) {
|
| 59 |
+
const bestMatchingDomain = getBestMatchingDomain(domain, locale, domainsConfig);
|
| 60 |
+
if (bestMatchingDomain) {
|
| 61 |
+
redirectDomain = bestMatchingDomain.domain;
|
| 62 |
+
const redirectDomainMode = bestMatchingDomain.localePrefix || resolvedRouting.localePrefix.mode;
|
| 63 |
+
if (bestMatchingDomain.defaultLocale === locale && redirectDomainMode === 'as-needed') {
|
| 64 |
+
urlObj.pathname = getNormalizedPathname(urlObj.pathname, resolvedRouting.locales, resolvedRouting.localePrefix);
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
if (redirectDomain) {
|
| 69 |
+
urlObj.host = redirectDomain;
|
| 70 |
+
if (request.headers.get('x-forwarded-host')) {
|
| 71 |
+
urlObj.protocol = request.headers.get('x-forwarded-proto') ?? request.nextUrl.protocol;
|
| 72 |
+
const redirectDomainPort = redirectDomain.split(':')[1];
|
| 73 |
+
urlObj.port = redirectDomainPort ?? request.headers.get('x-forwarded-port') ?? '';
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
if (request.nextUrl.basePath) {
|
| 77 |
+
urlObj.pathname = applyBasePath(urlObj.pathname, request.nextUrl.basePath);
|
| 78 |
+
}
|
| 79 |
+
hasRedirected = true;
|
| 80 |
+
return NextResponse.redirect(urlObj.toString());
|
| 81 |
+
}
|
| 82 |
+
const unprefixedExternalPathname = getNormalizedPathname(externalPathname, resolvedRouting.locales, resolvedRouting.localePrefix);
|
| 83 |
+
const pathnameMatch = getPathnameMatch(externalPathname, resolvedRouting.locales, resolvedRouting.localePrefix, domain);
|
| 84 |
+
const hasLocalePrefix = pathnameMatch != null;
|
| 85 |
+
|
| 86 |
+
// Use domain-specific localePrefix mode if available, otherwise use global mode
|
| 87 |
+
const effectiveLocalePrefixMode = domain?.localePrefix || resolvedRouting.localePrefix.mode;
|
| 88 |
+
const isUnprefixedRouting = effectiveLocalePrefixMode === 'never' || hasMatchedDefaultLocale && effectiveLocalePrefixMode === 'as-needed';
|
| 89 |
+
let response;
|
| 90 |
+
let internalTemplateName;
|
| 91 |
+
let hasRedirected;
|
| 92 |
+
let unprefixedInternalPathname = unprefixedExternalPathname;
|
| 93 |
+
const pathnames = resolvedRouting.pathnames;
|
| 94 |
+
if (pathnames) {
|
| 95 |
+
let resolvedTemplateLocale;
|
| 96 |
+
[resolvedTemplateLocale, internalTemplateName] = getInternalTemplate(pathnames, unprefixedExternalPathname, locale);
|
| 97 |
+
if (internalTemplateName) {
|
| 98 |
+
const pathnameConfig = pathnames[internalTemplateName];
|
| 99 |
+
const localeTemplate = getLocalizedTemplate(pathnameConfig, locale, internalTemplateName);
|
| 100 |
+
if (matchesPathname(localeTemplate, unprefixedExternalPathname)) {
|
| 101 |
+
unprefixedInternalPathname = formatTemplatePathname(unprefixedExternalPathname, localeTemplate, internalTemplateName);
|
| 102 |
+
} else {
|
| 103 |
+
let sourceTemplate;
|
| 104 |
+
if (resolvedTemplateLocale) {
|
| 105 |
+
// A localized pathname from another locale has matched
|
| 106 |
+
sourceTemplate = getLocalizedTemplate(pathnameConfig, resolvedTemplateLocale, internalTemplateName);
|
| 107 |
+
} else {
|
| 108 |
+
// An internal pathname has matched that
|
| 109 |
+
// doesn't have a localized pathname
|
| 110 |
+
sourceTemplate = internalTemplateName;
|
| 111 |
+
}
|
| 112 |
+
const localePrefix = isUnprefixedRouting ? undefined : getLocalePrefix(locale, resolvedRouting.localePrefix);
|
| 113 |
+
const template = formatTemplatePathname(unprefixedExternalPathname, sourceTemplate, localeTemplate);
|
| 114 |
+
response = redirect(formatPathname(template, localePrefix, request.nextUrl.search));
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
if (!response) {
|
| 119 |
+
if (unprefixedInternalPathname === '/' && !hasLocalePrefix) {
|
| 120 |
+
if (isUnprefixedRouting) {
|
| 121 |
+
response = next(formatPathname(unprefixedInternalPathname, getLocaleAsPrefix(locale), request.nextUrl.search));
|
| 122 |
+
} else {
|
| 123 |
+
response = redirect(formatPathname(unprefixedExternalPathname, getLocalePrefix(locale, resolvedRouting.localePrefix), request.nextUrl.search));
|
| 124 |
+
}
|
| 125 |
+
} else {
|
| 126 |
+
const internalHref = formatPathname(unprefixedInternalPathname, getLocaleAsPrefix(locale), request.nextUrl.search);
|
| 127 |
+
if (hasLocalePrefix) {
|
| 128 |
+
const externalHref = formatPathname(unprefixedExternalPathname, pathnameMatch.prefix, request.nextUrl.search);
|
| 129 |
+
if (effectiveLocalePrefixMode === 'never') {
|
| 130 |
+
response = redirect(formatPathname(unprefixedExternalPathname, undefined, request.nextUrl.search));
|
| 131 |
+
} else if (pathnameMatch.exact) {
|
| 132 |
+
if (hasMatchedDefaultLocale && isUnprefixedRouting) {
|
| 133 |
+
response = redirect(formatPathname(unprefixedExternalPathname, undefined, request.nextUrl.search));
|
| 134 |
+
} else {
|
| 135 |
+
if (resolvedRouting.domains) {
|
| 136 |
+
const pathDomain = getBestMatchingDomain(domain, pathnameMatch.locale, domainsConfig);
|
| 137 |
+
if (domain?.domain !== pathDomain?.domain && !hasUnknownHost) {
|
| 138 |
+
response = redirect(externalHref, pathDomain?.domain);
|
| 139 |
+
} else {
|
| 140 |
+
response = next(internalHref);
|
| 141 |
+
}
|
| 142 |
+
} else {
|
| 143 |
+
response = next(internalHref);
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
} else {
|
| 147 |
+
response = redirect(externalHref);
|
| 148 |
+
}
|
| 149 |
+
} else {
|
| 150 |
+
if (isUnprefixedRouting) {
|
| 151 |
+
response = next(internalHref);
|
| 152 |
+
} else {
|
| 153 |
+
response = redirect(formatPathname(unprefixedExternalPathname, getLocalePrefix(locale, resolvedRouting.localePrefix), request.nextUrl.search));
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
syncCookie(request, response, locale, resolvedRouting, domain);
|
| 159 |
+
if (!hasRedirected && effectiveLocalePrefixMode !== 'never' && resolvedRouting.alternateLinks && resolvedRouting.locales.length > 1) {
|
| 160 |
+
response.headers.set('Link', getAlternateLinksHeaderValue({
|
| 161 |
+
routing: resolvedRouting,
|
| 162 |
+
internalTemplateName,
|
| 163 |
+
localizedPathnames: internalTemplateName != null && pathnames ? pathnames[internalTemplateName] : undefined,
|
| 164 |
+
request,
|
| 165 |
+
resolvedLocale: locale
|
| 166 |
+
}));
|
| 167 |
+
}
|
| 168 |
+
return response;
|
| 169 |
+
};
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
export { createMiddleware as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/middleware/resolveLocale.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { match } from '@formatjs/intl-localematcher';
|
| 2 |
+
import Negotiator from 'negotiator';
|
| 3 |
+
import { getPathnameMatch, isLocaleSupportedOnDomain, getHost } from './utils.js';
|
| 4 |
+
|
| 5 |
+
function findDomainFromHost(requestHeaders, domains) {
|
| 6 |
+
const host = getHost(requestHeaders);
|
| 7 |
+
if (host) {
|
| 8 |
+
return domains.find(cur => cur.domain === host);
|
| 9 |
+
}
|
| 10 |
+
return undefined;
|
| 11 |
+
}
|
| 12 |
+
function orderLocales(locales) {
|
| 13 |
+
// Workaround for https://github.com/formatjs/formatjs/issues/4469
|
| 14 |
+
return locales.slice().sort((a, b) => b.length - a.length);
|
| 15 |
+
}
|
| 16 |
+
function mapToProvidedLocale(locales, locale) {
|
| 17 |
+
return locales.find(cur => cur.toLowerCase() === locale.toLowerCase());
|
| 18 |
+
}
|
| 19 |
+
function getAcceptLanguageLocale(requestHeaders, locales, defaultLocale) {
|
| 20 |
+
let locale;
|
| 21 |
+
const languages = new Negotiator({
|
| 22 |
+
headers: {
|
| 23 |
+
'accept-language': requestHeaders.get('accept-language') || undefined
|
| 24 |
+
}
|
| 25 |
+
}).languages();
|
| 26 |
+
try {
|
| 27 |
+
const orderedLocales = orderLocales(locales);
|
| 28 |
+
locale = mapToProvidedLocale(locales, match(languages, orderedLocales, defaultLocale));
|
| 29 |
+
} catch {
|
| 30 |
+
// Invalid language
|
| 31 |
+
}
|
| 32 |
+
return locale;
|
| 33 |
+
}
|
| 34 |
+
function getLocaleFromCookie(routing, requestCookies) {
|
| 35 |
+
if (routing.localeCookie && requestCookies.has(routing.localeCookie.name)) {
|
| 36 |
+
const value = requestCookies.get(routing.localeCookie.name)?.value;
|
| 37 |
+
if (value && routing.locales.includes(value)) {
|
| 38 |
+
return value;
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
function resolveLocaleFromPrefix(routing, requestHeaders, requestCookies, pathname) {
|
| 43 |
+
let locale;
|
| 44 |
+
|
| 45 |
+
// Prio 1: Use route prefix
|
| 46 |
+
if (pathname) {
|
| 47 |
+
locale = getPathnameMatch(pathname, routing.locales, routing.localePrefix)?.locale;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Prio 2: Use existing cookie
|
| 51 |
+
if (!locale && routing.localeDetection) {
|
| 52 |
+
locale = getLocaleFromCookie(routing, requestCookies);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// Prio 3: Use the `accept-language` header
|
| 56 |
+
if (!locale && routing.localeDetection) {
|
| 57 |
+
locale = getAcceptLanguageLocale(requestHeaders, routing.locales, routing.defaultLocale);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
// Prio 4: Use default locale
|
| 61 |
+
if (!locale) {
|
| 62 |
+
locale = routing.defaultLocale;
|
| 63 |
+
}
|
| 64 |
+
return locale;
|
| 65 |
+
}
|
| 66 |
+
function resolveLocaleFromDomain(routing, requestHeaders, requestCookies, pathname) {
|
| 67 |
+
const domains = routing.domains;
|
| 68 |
+
const domain = findDomainFromHost(requestHeaders, domains);
|
| 69 |
+
if (!domain) {
|
| 70 |
+
return {
|
| 71 |
+
locale: resolveLocaleFromPrefix(routing, requestHeaders, requestCookies, pathname)
|
| 72 |
+
};
|
| 73 |
+
}
|
| 74 |
+
let locale;
|
| 75 |
+
|
| 76 |
+
// Prio 1: Use route prefix
|
| 77 |
+
if (pathname) {
|
| 78 |
+
const prefixLocale = getPathnameMatch(pathname, routing.locales, routing.localePrefix, domain)?.locale;
|
| 79 |
+
if (prefixLocale) {
|
| 80 |
+
if (isLocaleSupportedOnDomain(prefixLocale, domain)) {
|
| 81 |
+
locale = prefixLocale;
|
| 82 |
+
} else {
|
| 83 |
+
// Causes a redirect to a domain that supports the locale
|
| 84 |
+
return {
|
| 85 |
+
locale: prefixLocale,
|
| 86 |
+
domain
|
| 87 |
+
};
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// Prio 2: Use existing cookie
|
| 93 |
+
if (!locale && routing.localeDetection) {
|
| 94 |
+
const cookieLocale = getLocaleFromCookie(routing, requestCookies);
|
| 95 |
+
if (cookieLocale) {
|
| 96 |
+
if (isLocaleSupportedOnDomain(cookieLocale, domain)) {
|
| 97 |
+
locale = cookieLocale;
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// Prio 3: Use the `accept-language` header
|
| 103 |
+
if (!locale && routing.localeDetection) {
|
| 104 |
+
const headerLocale = getAcceptLanguageLocale(requestHeaders, domain.locales, domain.defaultLocale);
|
| 105 |
+
if (headerLocale) {
|
| 106 |
+
locale = headerLocale;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
// Prio 4: Use default locale
|
| 111 |
+
if (!locale) {
|
| 112 |
+
locale = domain.defaultLocale;
|
| 113 |
+
}
|
| 114 |
+
return {
|
| 115 |
+
locale,
|
| 116 |
+
domain
|
| 117 |
+
};
|
| 118 |
+
}
|
| 119 |
+
function resolveLocale(routing, requestHeaders, requestCookies, pathname) {
|
| 120 |
+
if (routing.domains) {
|
| 121 |
+
return resolveLocaleFromDomain(routing, requestHeaders, requestCookies, pathname);
|
| 122 |
+
} else {
|
| 123 |
+
return {
|
| 124 |
+
locale: resolveLocaleFromPrefix(routing, requestHeaders, requestCookies, pathname)
|
| 125 |
+
};
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
export { resolveLocale as default, getAcceptLanguageLocale };
|
e-commerce/node_modules/next-intl/dist/esm/development/middleware/syncCookie.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getAcceptLanguageLocale } from './resolveLocale.js';
|
| 2 |
+
|
| 3 |
+
function syncCookie(request, response, locale, routing, domain) {
|
| 4 |
+
if (!routing.localeCookie) return;
|
| 5 |
+
|
| 6 |
+
// Only document requests indicate that the user is actually
|
| 7 |
+
// navigating to a page—in contrast to background requests of the
|
| 8 |
+
// client-side router like prefetches and cache revalidations.
|
| 9 |
+
// E.g. as of Next.js 16.3, the router revalidates previously
|
| 10 |
+
// visited routes after a navigation, including routes of a locale
|
| 11 |
+
// the user has just switched away from. Updating the cookie based
|
| 12 |
+
// on such a request would overwrite the locale that was just set
|
| 13 |
+
// when switching the locale. Note that Next.js strips router
|
| 14 |
+
// headers like `Next-Router-Prefetch` before invoking the
|
| 15 |
+
// middleware, therefore `Sec-Fetch-Dest` is used to detect
|
| 16 |
+
// background requests. Locale switches via a soft navigation
|
| 17 |
+
// update the cookie on the client side.
|
| 18 |
+
const secFetchDest = request.headers.get('sec-fetch-dest');
|
| 19 |
+
if (secFetchDest != null && secFetchDest !== 'document') return;
|
| 20 |
+
const {
|
| 21 |
+
name,
|
| 22 |
+
...rest
|
| 23 |
+
} = routing.localeCookie;
|
| 24 |
+
const hasLocaleCookie = request.cookies.has(name);
|
| 25 |
+
const hasOutdatedCookie = hasLocaleCookie && request.cookies.get(name)?.value !== locale;
|
| 26 |
+
if (hasOutdatedCookie) {
|
| 27 |
+
response.cookies.set(name, locale, {
|
| 28 |
+
path: request.nextUrl.basePath || undefined,
|
| 29 |
+
...rest
|
| 30 |
+
});
|
| 31 |
+
} else if (!hasLocaleCookie) {
|
| 32 |
+
const acceptLanguageLocale = getAcceptLanguageLocale(request.headers, domain?.locales || routing.locales, routing.defaultLocale);
|
| 33 |
+
if (acceptLanguageLocale !== locale) {
|
| 34 |
+
response.cookies.set(name, locale, {
|
| 35 |
+
path: request.nextUrl.basePath || undefined,
|
| 36 |
+
...rest
|
| 37 |
+
});
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export { syncCookie as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/middleware/utils.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { normalizeTrailingSlash, getSortedPathnames, matchesPathname, prefixPathname, getLocalePrefix, templateToRegex, getLocalizedTemplate } from '../shared/utils.js';
|
| 2 |
+
|
| 3 |
+
function getInternalTemplate(pathnames, pathname, locale) {
|
| 4 |
+
const sortedPathnames = getSortedPathnames(Object.keys(pathnames));
|
| 5 |
+
|
| 6 |
+
// Try to find a localized pathname that matches
|
| 7 |
+
for (const internalPathname of sortedPathnames) {
|
| 8 |
+
const localizedPathnamesOrPathname = pathnames[internalPathname];
|
| 9 |
+
if (typeof localizedPathnamesOrPathname === 'string') {
|
| 10 |
+
const localizedPathname = localizedPathnamesOrPathname;
|
| 11 |
+
if (matchesPathname(localizedPathname, pathname)) {
|
| 12 |
+
return [undefined, internalPathname];
|
| 13 |
+
}
|
| 14 |
+
} else {
|
| 15 |
+
// Prefer the entry with the current locale in case multiple
|
| 16 |
+
// localized pathnames match the current pathname
|
| 17 |
+
const sortedEntries = Object.entries(localizedPathnamesOrPathname);
|
| 18 |
+
const curLocaleIndex = sortedEntries.findIndex(([entryLocale]) => entryLocale === locale);
|
| 19 |
+
if (curLocaleIndex > 0) {
|
| 20 |
+
sortedEntries.unshift(sortedEntries.splice(curLocaleIndex, 1)[0]);
|
| 21 |
+
}
|
| 22 |
+
for (const [entryLocale] of sortedEntries) {
|
| 23 |
+
const localizedTemplate = getLocalizedTemplate(pathnames[internalPathname], entryLocale, internalPathname);
|
| 24 |
+
if (matchesPathname(localizedTemplate, pathname)) {
|
| 25 |
+
return [entryLocale, internalPathname];
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
// Try to find an internal pathname that matches (this can be the case
|
| 32 |
+
// if all localized pathnames are different from the internal pathnames)
|
| 33 |
+
for (const internalPathname of Object.keys(pathnames)) {
|
| 34 |
+
if (matchesPathname(internalPathname, pathname)) {
|
| 35 |
+
return [undefined, internalPathname];
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// No match
|
| 40 |
+
return [undefined, undefined];
|
| 41 |
+
}
|
| 42 |
+
function formatTemplatePathname(sourcePathname, sourceTemplate, targetTemplate, prefix) {
|
| 43 |
+
const params = getRouteParams(sourceTemplate, sourcePathname);
|
| 44 |
+
let targetPathname = '';
|
| 45 |
+
targetPathname += formatPathnameTemplate(targetTemplate, params);
|
| 46 |
+
|
| 47 |
+
// A pathname with an optional catchall like `/categories/[[...slug]]`
|
| 48 |
+
// should be normalized to `/categories` if the catchall is not present
|
| 49 |
+
// and no trailing slash is configured
|
| 50 |
+
targetPathname = normalizeTrailingSlash(targetPathname);
|
| 51 |
+
return targetPathname;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
/**
|
| 55 |
+
* Removes potential prefixes from the pathname.
|
| 56 |
+
*/
|
| 57 |
+
function getNormalizedPathname(pathname, locales, localePrefix) {
|
| 58 |
+
// Add trailing slash for consistent handling
|
| 59 |
+
// both for the root as well as nested paths
|
| 60 |
+
if (!pathname.endsWith('/')) {
|
| 61 |
+
pathname += '/';
|
| 62 |
+
}
|
| 63 |
+
const localePrefixes = getLocalePrefixes(locales, localePrefix);
|
| 64 |
+
const regex = new RegExp(`^(${localePrefixes.map(([, prefix]) => prefix.replaceAll('/', '\\/')).join('|')})/(.*)`, 'i');
|
| 65 |
+
const match = pathname.match(regex);
|
| 66 |
+
let result = match ? '/' + match[2] : pathname;
|
| 67 |
+
if (result !== '/') {
|
| 68 |
+
result = normalizeTrailingSlash(result);
|
| 69 |
+
}
|
| 70 |
+
return result;
|
| 71 |
+
}
|
| 72 |
+
function getLocalePrefixes(locales, localePrefix, sort = true) {
|
| 73 |
+
const prefixes = locales.map(locale => [locale, getLocalePrefix(locale, localePrefix)]);
|
| 74 |
+
if (sort) {
|
| 75 |
+
// More specific ones first
|
| 76 |
+
prefixes.sort((a, b) => b[1].length - a[1].length);
|
| 77 |
+
}
|
| 78 |
+
return prefixes;
|
| 79 |
+
}
|
| 80 |
+
function getPathnameMatch(pathname, locales, localePrefix, domain) {
|
| 81 |
+
const localePrefixes = getLocalePrefixes(locales, localePrefix);
|
| 82 |
+
|
| 83 |
+
// Sort to prioritize domain locales
|
| 84 |
+
if (domain) {
|
| 85 |
+
localePrefixes.sort(([localeA], [localeB]) => {
|
| 86 |
+
if (localeA === domain.defaultLocale) return -1;
|
| 87 |
+
if (localeB === domain.defaultLocale) return 1;
|
| 88 |
+
const isLocaleAInDomain = domain.locales.includes(localeA);
|
| 89 |
+
const isLocaleBInDomain = domain.locales.includes(localeB);
|
| 90 |
+
if (isLocaleAInDomain && !isLocaleBInDomain) return -1;
|
| 91 |
+
if (!isLocaleAInDomain && isLocaleBInDomain) return 1;
|
| 92 |
+
return 0;
|
| 93 |
+
});
|
| 94 |
+
}
|
| 95 |
+
for (const [locale, prefix] of localePrefixes) {
|
| 96 |
+
let exact, matches;
|
| 97 |
+
if (pathname === prefix || pathname.startsWith(prefix + '/')) {
|
| 98 |
+
exact = matches = true;
|
| 99 |
+
} else {
|
| 100 |
+
const normalizedPathname = pathname.toLowerCase();
|
| 101 |
+
const normalizedPrefix = prefix.toLowerCase();
|
| 102 |
+
if (normalizedPathname === normalizedPrefix || normalizedPathname.startsWith(normalizedPrefix + '/')) {
|
| 103 |
+
exact = false;
|
| 104 |
+
matches = true;
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
if (matches) {
|
| 108 |
+
return {
|
| 109 |
+
locale,
|
| 110 |
+
prefix,
|
| 111 |
+
matchedPrefix: pathname.slice(0, prefix.length),
|
| 112 |
+
exact
|
| 113 |
+
};
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
function getRouteParams(template, pathname) {
|
| 118 |
+
const normalizedPathname = normalizeTrailingSlash(pathname);
|
| 119 |
+
const normalizedTemplate = normalizeTrailingSlash(template);
|
| 120 |
+
const regex = templateToRegex(normalizedTemplate);
|
| 121 |
+
const match = regex.exec(normalizedPathname);
|
| 122 |
+
if (!match) return undefined;
|
| 123 |
+
const params = {};
|
| 124 |
+
const keys = normalizedTemplate.match(/\[([^\]]+)\]/g) ?? [];
|
| 125 |
+
for (let i = 1; i < match.length; i++) {
|
| 126 |
+
const rawKey = keys[i - 1];
|
| 127 |
+
if (!rawKey) continue;
|
| 128 |
+
const key = rawKey.replace(/[[\]]/g, '');
|
| 129 |
+
const value = match[i] ?? '';
|
| 130 |
+
params[key] = value;
|
| 131 |
+
}
|
| 132 |
+
return params;
|
| 133 |
+
}
|
| 134 |
+
function formatPathnameTemplate(template, params) {
|
| 135 |
+
if (!params) return template;
|
| 136 |
+
|
| 137 |
+
// Simplify syntax for optional catchall ('[[...slug]]') so
|
| 138 |
+
// we can replace the value with simple interpolation
|
| 139 |
+
template = template.replace(/\[\[/g, '[').replace(/\]\]/g, ']');
|
| 140 |
+
let result = template;
|
| 141 |
+
Object.entries(params).forEach(([key, value]) => {
|
| 142 |
+
result = result.replace(`[${key}]`, () => value);
|
| 143 |
+
});
|
| 144 |
+
return result;
|
| 145 |
+
}
|
| 146 |
+
function formatPathname(pathname, prefix, search) {
|
| 147 |
+
let result = pathname;
|
| 148 |
+
if (prefix) {
|
| 149 |
+
result = prefixPathname(prefix, result);
|
| 150 |
+
}
|
| 151 |
+
if (search) {
|
| 152 |
+
result += search;
|
| 153 |
+
}
|
| 154 |
+
return result;
|
| 155 |
+
}
|
| 156 |
+
function getHost(requestHeaders) {
|
| 157 |
+
return requestHeaders.get('x-forwarded-host') ?? requestHeaders.get('host') ?? undefined;
|
| 158 |
+
}
|
| 159 |
+
function isLocaleSupportedOnDomain(locale, domain) {
|
| 160 |
+
return domain.defaultLocale === locale || domain.locales.includes(locale);
|
| 161 |
+
}
|
| 162 |
+
function getBestMatchingDomain(curHostDomain, locale, domainsConfig) {
|
| 163 |
+
let domainConfig;
|
| 164 |
+
|
| 165 |
+
// Prio 1: Stay on current domain
|
| 166 |
+
if (curHostDomain && isLocaleSupportedOnDomain(locale, curHostDomain)) {
|
| 167 |
+
domainConfig = curHostDomain;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
// Prio 2: Use alternative domain with matching default locale
|
| 171 |
+
if (!domainConfig) {
|
| 172 |
+
domainConfig = domainsConfig.find(cur => cur.defaultLocale === locale);
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
// Prio 3: Use alternative domain that supports the locale
|
| 176 |
+
if (!domainConfig) {
|
| 177 |
+
domainConfig = domainsConfig.find(cur => cur.locales.includes(locale));
|
| 178 |
+
}
|
| 179 |
+
return domainConfig;
|
| 180 |
+
}
|
| 181 |
+
function applyBasePath(pathname, basePath) {
|
| 182 |
+
return normalizeTrailingSlash(basePath + pathname);
|
| 183 |
+
}
|
| 184 |
+
function getLocaleAsPrefix(locale) {
|
| 185 |
+
return `/${locale}`;
|
| 186 |
+
}
|
| 187 |
+
function sanitizePathname(pathname) {
|
| 188 |
+
// Sanitize malicious URIs, e.g.:
|
| 189 |
+
// '/en/\\example.org' → '/en/%5Cexample.org' (backslash → %5C)
|
| 190 |
+
// '/en/\t/example.org' → '/en/example.org' (WHATWG-stripped TAB)
|
| 191 |
+
// '/en/\n/example.org' → '/en/example.org' (WHATWG-stripped LF)
|
| 192 |
+
// '/en/\r/example.org' → '/en/example.org' (WHATWG-stripped CR)
|
| 193 |
+
// '/en////example.org' → '/en/example.org' (consecutive slashes)
|
| 194 |
+
//
|
| 195 |
+
// U+0009/000A/000D are silently stripped by the WHATWG URL parser
|
| 196 |
+
// (https://url.spec.whatwg.org/#concept-url-parser). Without removing
|
| 197 |
+
// them here, a decoded TAB in a segment separator position causes
|
| 198 |
+
// new URL("/\t/host", base) to collapse to "//host" → open redirect.
|
| 199 |
+
return pathname.replace(/\\/g, '%5C').replace(/[\t\n\r]/g, '').replace(/\/+/g, '/');
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
export { applyBasePath, formatPathname, formatPathnameTemplate, formatTemplatePathname, getBestMatchingDomain, getHost, getInternalTemplate, getLocaleAsPrefix, getLocalePrefixes, getNormalizedPathname, getPathnameMatch, getRouteParams, isLocaleSupportedOnDomain, sanitizePathname };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation.react-client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export { default as createNavigation } from './navigation/react-client/createNavigation.js';
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation.react-server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export { default as createNavigation } from './navigation/react-server/createNavigation.js';
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-client/createNavigation.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useRouter } from 'next/navigation';
|
| 2 |
+
import { useMemo } from 'react';
|
| 3 |
+
import { useLocale } from 'use-intl';
|
| 4 |
+
import createSharedNavigationFns from '../shared/createSharedNavigationFns.js';
|
| 5 |
+
import syncLocaleCookie from '../shared/syncLocaleCookie.js';
|
| 6 |
+
import { getRoute } from '../shared/utils.js';
|
| 7 |
+
import useBasePathname from './useBasePathname.js';
|
| 8 |
+
|
| 9 |
+
function createNavigation(routing) {
|
| 10 |
+
const {
|
| 11 |
+
Link,
|
| 12 |
+
config,
|
| 13 |
+
getPathname,
|
| 14 |
+
...redirects
|
| 15 |
+
} = createSharedNavigationFns(useLocale, routing);
|
| 16 |
+
|
| 17 |
+
/** @see https://next-intl.dev/docs/routing/navigation#usepathname */
|
| 18 |
+
function usePathname() {
|
| 19 |
+
const pathname = useBasePathname(config);
|
| 20 |
+
const locale = useLocale();
|
| 21 |
+
|
| 22 |
+
// @ts-expect-error -- Mirror the behavior from Next.js, where `null` is returned when `usePathname` is used outside of Next, but the types indicate that a string is always returned.
|
| 23 |
+
return useMemo(() => pathname &&
|
| 24 |
+
// @ts-expect-error -- This is fine
|
| 25 |
+
config.pathnames ? getRoute(locale, pathname,
|
| 26 |
+
// @ts-expect-error -- This is fine
|
| 27 |
+
config.pathnames) : pathname, [locale, pathname]);
|
| 28 |
+
}
|
| 29 |
+
function useRouter$1() {
|
| 30 |
+
const router = useRouter();
|
| 31 |
+
const curLocale = useLocale();
|
| 32 |
+
return useMemo(() => {
|
| 33 |
+
function createHandler(fn) {
|
| 34 |
+
return function handler(href, options) {
|
| 35 |
+
const {
|
| 36 |
+
locale: nextLocale,
|
| 37 |
+
...rest
|
| 38 |
+
} = options || {};
|
| 39 |
+
const pathname = getPathname({
|
| 40 |
+
href,
|
| 41 |
+
locale: nextLocale || curLocale,
|
| 42 |
+
// Always include a prefix when changing locales. Theoretically,
|
| 43 |
+
// this is only necessary for the case described in #2020. However,
|
| 44 |
+
// the full detection is rather expensive, and this behavior is
|
| 45 |
+
// consistent with the `Link` component. The downside is an
|
| 46 |
+
// additional redirect for users in other situations. Locale
|
| 47 |
+
// changes should be rare though, so this might be fine.
|
| 48 |
+
forcePrefix: nextLocale != null || undefined
|
| 49 |
+
});
|
| 50 |
+
const args = [pathname];
|
| 51 |
+
if (Object.keys(rest).length > 0) {
|
| 52 |
+
// @ts-expect-error -- This is fine
|
| 53 |
+
args.push(rest);
|
| 54 |
+
}
|
| 55 |
+
syncLocaleCookie(config.localeCookie, curLocale, nextLocale);
|
| 56 |
+
fn(...args);
|
| 57 |
+
};
|
| 58 |
+
}
|
| 59 |
+
return {
|
| 60 |
+
...router,
|
| 61 |
+
/** @see https://next-intl.dev/docs/routing/navigation#userouter */
|
| 62 |
+
push: createHandler(router.push),
|
| 63 |
+
/** @see https://next-intl.dev/docs/routing/navigation#userouter */
|
| 64 |
+
replace: createHandler(router.replace),
|
| 65 |
+
/** @see https://next-intl.dev/docs/routing/navigation#userouter */
|
| 66 |
+
prefetch: createHandler(router.prefetch)
|
| 67 |
+
};
|
| 68 |
+
}, [curLocale, router]);
|
| 69 |
+
}
|
| 70 |
+
return {
|
| 71 |
+
...redirects,
|
| 72 |
+
Link,
|
| 73 |
+
usePathname,
|
| 74 |
+
useRouter: useRouter$1,
|
| 75 |
+
getPathname
|
| 76 |
+
};
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
export { createNavigation as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-client/useBasePathname.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { usePathname } from 'next/navigation';
|
| 2 |
+
import { useMemo } from 'react';
|
| 3 |
+
import { useLocale } from 'use-intl';
|
| 4 |
+
import { hasPathnamePrefixed, unprefixPathname, getLocalePrefix, getLocaleAsPrefix } from '../../shared/utils.js';
|
| 5 |
+
|
| 6 |
+
function useBasePathname(config) {
|
| 7 |
+
// The types aren't entirely correct here. Outside of Next.js
|
| 8 |
+
// `useParams` can be called, but the return type is `null`.
|
| 9 |
+
|
| 10 |
+
// Notes on `useNextPathname`:
|
| 11 |
+
// - Types aren't entirely correct. Outside of Next.js the
|
| 12 |
+
// hook will return `null` (e.g. unit tests)
|
| 13 |
+
// - A base path is stripped from the result
|
| 14 |
+
// - Rewrites *are* taken into account (i.e. the pathname
|
| 15 |
+
// that the user sees in the browser is returned)
|
| 16 |
+
const pathname = usePathname();
|
| 17 |
+
const locale = useLocale();
|
| 18 |
+
return useMemo(() => {
|
| 19 |
+
if (!pathname) return pathname;
|
| 20 |
+
let unlocalizedPathname = pathname;
|
| 21 |
+
const prefix = getLocalePrefix(locale, config.localePrefix);
|
| 22 |
+
const isPathnamePrefixed = hasPathnamePrefixed(prefix, pathname);
|
| 23 |
+
if (isPathnamePrefixed) {
|
| 24 |
+
unlocalizedPathname = unprefixPathname(pathname, prefix);
|
| 25 |
+
} else if (config.localePrefix.mode !== 'never' && config.localePrefix.prefixes) {
|
| 26 |
+
// Workaround for https://github.com/vercel/next.js/issues/73085
|
| 27 |
+
const localeAsPrefix = getLocaleAsPrefix(locale);
|
| 28 |
+
if (hasPathnamePrefixed(localeAsPrefix, pathname)) {
|
| 29 |
+
unlocalizedPathname = unprefixPathname(pathname, localeAsPrefix);
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
return unlocalizedPathname;
|
| 33 |
+
}, [config.localePrefix, locale, pathname]);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
export { useBasePathname as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-server/createNavigation.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import createSharedNavigationFns from '../shared/createSharedNavigationFns.js';
|
| 2 |
+
import getServerLocale from './getServerLocale.js';
|
| 3 |
+
|
| 4 |
+
function createNavigation(routing) {
|
| 5 |
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
| 6 |
+
const {
|
| 7 |
+
config,
|
| 8 |
+
...fns
|
| 9 |
+
} = createSharedNavigationFns(getServerLocale, routing);
|
| 10 |
+
function notSupported(hookName) {
|
| 11 |
+
return () => {
|
| 12 |
+
throw new Error(`\`${hookName}\` is not supported in Server Components. You can use this hook if you convert the calling component to a Client Component.`);
|
| 13 |
+
};
|
| 14 |
+
}
|
| 15 |
+
return {
|
| 16 |
+
...fns,
|
| 17 |
+
usePathname: notSupported('usePathname'),
|
| 18 |
+
useRouter: notSupported('useRouter')
|
| 19 |
+
};
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export { createNavigation as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/react-server/getServerLocale.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import getConfig from '../../server/react-server/getConfig.js';
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* This is only moved to a separate module for easier mocking in
|
| 5 |
+
* `../createNavigatoin.test.tsx` in order to avoid suspending.
|
| 6 |
+
*/
|
| 7 |
+
async function getServerLocale() {
|
| 8 |
+
const config = await getConfig();
|
| 9 |
+
return config.locale;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export { getServerLocale as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/BaseLink.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import NextLink from 'next/link';
|
| 3 |
+
import { forwardRef } from 'react';
|
| 4 |
+
import { useLocale } from 'use-intl';
|
| 5 |
+
import syncLocaleCookie from './syncLocaleCookie.js';
|
| 6 |
+
import { jsx } from 'react/jsx-runtime';
|
| 7 |
+
|
| 8 |
+
// Somehow the types for `next/link` don't work as expected
|
| 9 |
+
// when `moduleResolution: "nodenext"` is used.
|
| 10 |
+
const Link = NextLink;
|
| 11 |
+
|
| 12 |
+
// Links that change the locale are handled in a separate component,
|
| 13 |
+
// since they require additional handling for syncing the locale cookie.
|
| 14 |
+
function LocaleChangingLink({
|
| 15 |
+
curLocale,
|
| 16 |
+
linkRef,
|
| 17 |
+
locale,
|
| 18 |
+
localeCookie,
|
| 19 |
+
onClick,
|
| 20 |
+
prefetch,
|
| 21 |
+
...rest
|
| 22 |
+
}) {
|
| 23 |
+
function onLinkClick(event) {
|
| 24 |
+
// Even though we force a prefix when changing locales,
|
| 25 |
+
// this could be a cache hit of the client-side router,
|
| 26 |
+
// therefore we sync the cookie to ensure it's up to date.
|
| 27 |
+
syncLocaleCookie(localeCookie, curLocale, locale);
|
| 28 |
+
if (onClick) onClick(event);
|
| 29 |
+
}
|
| 30 |
+
if (prefetch && "development" !== 'production') {
|
| 31 |
+
console.error('The `prefetch` prop is currently not supported when using the `locale` prop on `Link` to switch the locale.`');
|
| 32 |
+
}
|
| 33 |
+
return /*#__PURE__*/jsx(Link, {
|
| 34 |
+
ref: linkRef,
|
| 35 |
+
hrefLang: locale,
|
| 36 |
+
onClick: onLinkClick,
|
| 37 |
+
prefetch: false,
|
| 38 |
+
...rest
|
| 39 |
+
});
|
| 40 |
+
}
|
| 41 |
+
function BaseLink({
|
| 42 |
+
locale,
|
| 43 |
+
localeCookie,
|
| 44 |
+
...rest
|
| 45 |
+
}, ref) {
|
| 46 |
+
const curLocale = useLocale();
|
| 47 |
+
const isChangingLocale = locale != null && locale !== curLocale;
|
| 48 |
+
if (isChangingLocale) {
|
| 49 |
+
return /*#__PURE__*/jsx(LocaleChangingLink, {
|
| 50 |
+
curLocale: curLocale,
|
| 51 |
+
linkRef: ref,
|
| 52 |
+
locale: locale,
|
| 53 |
+
localeCookie: localeCookie,
|
| 54 |
+
...rest
|
| 55 |
+
});
|
| 56 |
+
}
|
| 57 |
+
return /*#__PURE__*/jsx(Link, {
|
| 58 |
+
ref: ref,
|
| 59 |
+
...rest
|
| 60 |
+
});
|
| 61 |
+
}
|
| 62 |
+
var BaseLink$1 = /*#__PURE__*/forwardRef(BaseLink);
|
| 63 |
+
|
| 64 |
+
export { BaseLink$1 as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/createSharedNavigationFns.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { redirect, permanentRedirect } from 'next/navigation';
|
| 2 |
+
import { forwardRef } from 'react';
|
| 3 |
+
import { receiveRoutingConfig } from '../../routing/config.js';
|
| 4 |
+
import use from '../../shared/use.js';
|
| 5 |
+
import { isLocalizableHref, isPromise } from '../../shared/utils.js';
|
| 6 |
+
import BaseLink from './BaseLink.js';
|
| 7 |
+
import { validateReceivedConfig, serializeSearchParams, compileLocalizedPathname, applyPathnamePrefix, normalizeNameOrNameWithParams } from './utils.js';
|
| 8 |
+
import { jsx } from 'react/jsx-runtime';
|
| 9 |
+
|
| 10 |
+
/**
|
| 11 |
+
* Shared implementations for `react-server` and `react-client`
|
| 12 |
+
*/
|
| 13 |
+
function createSharedNavigationFns(getLocale, routing) {
|
| 14 |
+
const config = receiveRoutingConfig(routing || {});
|
| 15 |
+
{
|
| 16 |
+
validateReceivedConfig(config);
|
| 17 |
+
}
|
| 18 |
+
const pathnames = config.pathnames;
|
| 19 |
+
function Link({
|
| 20 |
+
href,
|
| 21 |
+
locale,
|
| 22 |
+
...rest
|
| 23 |
+
}, ref) {
|
| 24 |
+
let pathname, params;
|
| 25 |
+
if (typeof href === 'object') {
|
| 26 |
+
pathname = href.pathname;
|
| 27 |
+
// @ts-expect-error -- This is ok
|
| 28 |
+
params = href.params;
|
| 29 |
+
} else {
|
| 30 |
+
pathname = href;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// @ts-expect-error -- This is ok
|
| 34 |
+
const isLocalizable = isLocalizableHref(href);
|
| 35 |
+
const localePromiseOrValue = getLocale();
|
| 36 |
+
const curLocale = isPromise(localePromiseOrValue) ? use(localePromiseOrValue) : localePromiseOrValue;
|
| 37 |
+
const finalPathname = isLocalizable ? getPathname({
|
| 38 |
+
locale: locale || curLocale,
|
| 39 |
+
// @ts-expect-error -- This is ok
|
| 40 |
+
href: pathnames == null ? pathname : {
|
| 41 |
+
pathname,
|
| 42 |
+
params
|
| 43 |
+
},
|
| 44 |
+
// Always include a prefix when changing locales
|
| 45 |
+
forcePrefix: locale != null || undefined
|
| 46 |
+
}) : pathname;
|
| 47 |
+
return /*#__PURE__*/jsx(BaseLink, {
|
| 48 |
+
ref: ref
|
| 49 |
+
// @ts-expect-error -- This is ok
|
| 50 |
+
,
|
| 51 |
+
href: typeof href === 'object' ? {
|
| 52 |
+
...href,
|
| 53 |
+
pathname: finalPathname
|
| 54 |
+
} : finalPathname,
|
| 55 |
+
locale: locale,
|
| 56 |
+
localeCookie: config.localeCookie,
|
| 57 |
+
...rest
|
| 58 |
+
});
|
| 59 |
+
}
|
| 60 |
+
const LinkWithRef = /*#__PURE__*/forwardRef(Link);
|
| 61 |
+
function getPathname(args) {
|
| 62 |
+
const {
|
| 63 |
+
forcePrefix,
|
| 64 |
+
href,
|
| 65 |
+
locale
|
| 66 |
+
} = args;
|
| 67 |
+
let pathname;
|
| 68 |
+
if (pathnames == null) {
|
| 69 |
+
if (typeof href === 'object') {
|
| 70 |
+
pathname = href.pathname;
|
| 71 |
+
if (href.query) {
|
| 72 |
+
pathname += serializeSearchParams(href.query);
|
| 73 |
+
}
|
| 74 |
+
} else {
|
| 75 |
+
pathname = href;
|
| 76 |
+
}
|
| 77 |
+
} else {
|
| 78 |
+
pathname = compileLocalizedPathname({
|
| 79 |
+
locale,
|
| 80 |
+
// @ts-expect-error -- This is ok
|
| 81 |
+
...normalizeNameOrNameWithParams(href),
|
| 82 |
+
// @ts-expect-error -- This is ok
|
| 83 |
+
pathnames: config.pathnames
|
| 84 |
+
});
|
| 85 |
+
}
|
| 86 |
+
return applyPathnamePrefix(pathname, locale, config, forcePrefix);
|
| 87 |
+
}
|
| 88 |
+
function getRedirectFn(fn) {
|
| 89 |
+
/** @see https://next-intl.dev/docs/routing/navigation#redirect */
|
| 90 |
+
return function redirectFn(args, ...rest) {
|
| 91 |
+
return fn(getPathname(args), ...rest);
|
| 92 |
+
};
|
| 93 |
+
}
|
| 94 |
+
const redirect$1 = getRedirectFn(redirect);
|
| 95 |
+
const permanentRedirect$1 = getRedirectFn(permanentRedirect);
|
| 96 |
+
return {
|
| 97 |
+
config,
|
| 98 |
+
Link: LinkWithRef,
|
| 99 |
+
redirect: redirect$1,
|
| 100 |
+
permanentRedirect: permanentRedirect$1,
|
| 101 |
+
getPathname
|
| 102 |
+
};
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
export { createSharedNavigationFns as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/syncLocaleCookie.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getBasePath } from './utils.js';
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* We have to keep the cookie value in sync as Next.js might
|
| 5 |
+
* skip a request to the server due to its router cache.
|
| 6 |
+
* See https://github.com/amannn/next-intl/issues/786.
|
| 7 |
+
*/
|
| 8 |
+
function syncLocaleCookie(localeCookie, locale, nextLocale) {
|
| 9 |
+
const isSwitchingLocale = nextLocale !== locale && nextLocale != null;
|
| 10 |
+
if (!localeCookie || !isSwitchingLocale) {
|
| 11 |
+
return;
|
| 12 |
+
}
|
| 13 |
+
const {
|
| 14 |
+
name,
|
| 15 |
+
...rest
|
| 16 |
+
} = localeCookie;
|
| 17 |
+
if (!rest.path) {
|
| 18 |
+
rest.path = getBasePath() || '/';
|
| 19 |
+
}
|
| 20 |
+
let localeCookieString = `${name}=${nextLocale};`;
|
| 21 |
+
for (const [key, value] of Object.entries(rest)) {
|
| 22 |
+
// Map object properties to cookie properties.
|
| 23 |
+
// Interestingly, `maxAge` corresponds to `max-age`,
|
| 24 |
+
// while `sameSite` corresponds to `SameSite`.
|
| 25 |
+
// Also, keys are case-insensitive.
|
| 26 |
+
const targetKey = key === 'maxAge' ? 'max-age' : key;
|
| 27 |
+
localeCookieString += `${targetKey}`;
|
| 28 |
+
if (typeof value !== 'boolean') {
|
| 29 |
+
localeCookieString += '=' + value;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// A trailing ";" is allowed by browsers
|
| 33 |
+
localeCookieString += ';';
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// Note that writing to `document.cookie` doesn't overwrite all
|
| 37 |
+
// cookies, but only the ones referenced via the name here.
|
| 38 |
+
document.cookie = localeCookieString;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
export { syncLocaleCookie as default };
|
e-commerce/node_modules/next-intl/dist/esm/development/navigation/shared/utils.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getSortedPathnames, matchesPathname, isLocalizableHref, prefixPathname, getLocalizedTemplate, normalizeTrailingSlash, getLocalePrefix } from '../../shared/utils.js';
|
| 2 |
+
|
| 3 |
+
// Minor false positive: A route that has both optional and
|
| 4 |
+
// required params will allow optional params.
|
| 5 |
+
|
| 6 |
+
// For `Link`
|
| 7 |
+
|
| 8 |
+
// For `getPathname` (hence also its consumers: `redirect`, `useRouter`, …)
|
| 9 |
+
|
| 10 |
+
function normalizeNameOrNameWithParams(href) {
|
| 11 |
+
return typeof href === 'string' ? {
|
| 12 |
+
pathname: href
|
| 13 |
+
} : href;
|
| 14 |
+
}
|
| 15 |
+
function serializeSearchParams(searchParams) {
|
| 16 |
+
function serializeValue(value) {
|
| 17 |
+
return String(value);
|
| 18 |
+
}
|
| 19 |
+
const urlSearchParams = new URLSearchParams();
|
| 20 |
+
for (const [key, value] of Object.entries(searchParams)) {
|
| 21 |
+
if (Array.isArray(value)) {
|
| 22 |
+
value.forEach(cur => {
|
| 23 |
+
urlSearchParams.append(key, serializeValue(cur));
|
| 24 |
+
});
|
| 25 |
+
} else {
|
| 26 |
+
urlSearchParams.set(key, serializeValue(value));
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
return '?' + urlSearchParams.toString();
|
| 30 |
+
}
|
| 31 |
+
function compileLocalizedPathname({
|
| 32 |
+
pathname,
|
| 33 |
+
locale,
|
| 34 |
+
params,
|
| 35 |
+
pathnames,
|
| 36 |
+
query
|
| 37 |
+
}) {
|
| 38 |
+
function compilePath(value) {
|
| 39 |
+
const pathnameConfig = pathnames[value];
|
| 40 |
+
let compiled;
|
| 41 |
+
if (pathnameConfig) {
|
| 42 |
+
const template = getLocalizedTemplate(pathnameConfig, locale, value);
|
| 43 |
+
compiled = template;
|
| 44 |
+
if (params) {
|
| 45 |
+
Object.entries(params).forEach(([key, paramValue]) => {
|
| 46 |
+
let regexp, replacer;
|
| 47 |
+
if (Array.isArray(paramValue)) {
|
| 48 |
+
regexp = `(\\[)?\\[...${key}\\](\\])?`;
|
| 49 |
+
replacer = paramValue.map(v => String(v)).join('/');
|
| 50 |
+
} else {
|
| 51 |
+
regexp = `\\[${key}\\]`;
|
| 52 |
+
replacer = String(paramValue);
|
| 53 |
+
}
|
| 54 |
+
compiled = compiled.replace(new RegExp(regexp, 'g'), () => replacer);
|
| 55 |
+
});
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Clean up optional catch-all segments that were not replaced
|
| 59 |
+
compiled = compiled.replace(/\[\[\.\.\..+\]\]/g, '');
|
| 60 |
+
if (compiled.includes('[')) {
|
| 61 |
+
// Next.js throws anyway, therefore better provide a more helpful error message
|
| 62 |
+
throw new Error(`Insufficient params provided for localized pathname.\nTemplate: ${template}\nParams: ${JSON.stringify(params)}`);
|
| 63 |
+
}
|
| 64 |
+
compiled = encodePathname(compiled);
|
| 65 |
+
} else {
|
| 66 |
+
// Unknown pathnames
|
| 67 |
+
compiled = value;
|
| 68 |
+
}
|
| 69 |
+
compiled = normalizeTrailingSlash(compiled);
|
| 70 |
+
if (query) {
|
| 71 |
+
// This also encodes non-ASCII characters by
|
| 72 |
+
// using `new URLSearchParams()` internally
|
| 73 |
+
compiled += serializeSearchParams(query);
|
| 74 |
+
}
|
| 75 |
+
return compiled;
|
| 76 |
+
}
|
| 77 |
+
if (typeof pathname === 'string') {
|
| 78 |
+
return compilePath(pathname);
|
| 79 |
+
} else {
|
| 80 |
+
const {
|
| 81 |
+
pathname: internalPathname,
|
| 82 |
+
...rest
|
| 83 |
+
} = pathname;
|
| 84 |
+
const compiled = compilePath(internalPathname);
|
| 85 |
+
const result = {
|
| 86 |
+
...rest,
|
| 87 |
+
pathname: compiled
|
| 88 |
+
};
|
| 89 |
+
return result;
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
function encodePathname(pathname) {
|
| 93 |
+
// Generally, to comply with RFC 3986 and Google's best practices for URL structures
|
| 94 |
+
// (https://developers.google.com/search/docs/crawling-indexing/url-structure),
|
| 95 |
+
// we should always encode non-ASCII characters.
|
| 96 |
+
//
|
| 97 |
+
// There are two places where next-intl interacts with potentially non-ASCII URLs:
|
| 98 |
+
// 1. Middleware: When mapping a localized pathname to a non-localized pathname internally
|
| 99 |
+
// 2. Navigation APIs: When generating a URLs to be used for <Link /> & friends
|
| 100 |
+
//
|
| 101 |
+
// Next.js normalizes incoming pathnames to always be encoded, therefore we can safely
|
| 102 |
+
// decode them there (see middleware.tsx). On the other hand, Next.js doesn't consistently
|
| 103 |
+
// encode non-ASCII characters that are passed to navigation APIs:
|
| 104 |
+
// 1. <Link /> doesn't encode non-ASCII characters
|
| 105 |
+
// 2. useRouter() uses `new URL()` internally, which will encode—but only if necessary
|
| 106 |
+
// 3. redirect() uses useRouter() on the client, but on the server side only
|
| 107 |
+
// assigns the location header without encoding.
|
| 108 |
+
//
|
| 109 |
+
// In addition to this, for getPathname() we need to encode non-ASCII characters.
|
| 110 |
+
//
|
| 111 |
+
// Therefore, the bottom line is that next-intl should take care of encoding non-ASCII
|
| 112 |
+
// characters in all cases, but can rely on `new URL()` to not double-encode characters.
|
| 113 |
+
return new URL(pathname, 'http://l').pathname;
|
| 114 |
+
}
|
| 115 |
+
function getRoute(locale, pathname, pathnames) {
|
| 116 |
+
const sortedPathnames = getSortedPathnames(Object.keys(pathnames));
|
| 117 |
+
const decoded = decodeURI(pathname);
|
| 118 |
+
for (const internalPathname of sortedPathnames) {
|
| 119 |
+
const localizedPathnamesOrPathname = pathnames[internalPathname];
|
| 120 |
+
if (typeof localizedPathnamesOrPathname === 'string') {
|
| 121 |
+
const localizedPathname = localizedPathnamesOrPathname;
|
| 122 |
+
if (matchesPathname(localizedPathname, decoded)) {
|
| 123 |
+
return internalPathname;
|
| 124 |
+
}
|
| 125 |
+
} else {
|
| 126 |
+
if (matchesPathname(getLocalizedTemplate(localizedPathnamesOrPathname, locale, internalPathname), decoded)) {
|
| 127 |
+
return internalPathname;
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
return pathname;
|
| 132 |
+
}
|
| 133 |
+
function getBasePath() {
|
| 134 |
+
try {
|
| 135 |
+
// Provided via `env` setting in `next.config.js` via the plugin
|
| 136 |
+
return process.env._next_intl_base_path || '';
|
| 137 |
+
} catch {
|
| 138 |
+
return '';
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
function applyPathnamePrefix(pathname, locale, routing, force) {
|
| 142 |
+
const {
|
| 143 |
+
mode: routingMode
|
| 144 |
+
} = routing.localePrefix;
|
| 145 |
+
let shouldPrefix;
|
| 146 |
+
if (force !== undefined) {
|
| 147 |
+
shouldPrefix = force;
|
| 148 |
+
} else if (isLocalizableHref(pathname)) {
|
| 149 |
+
// Since locales are unique per domain, there should be
|
| 150 |
+
// only one domain that contains the locale
|
| 151 |
+
const domain = routing.domains?.find(cur => cur.locales.includes(locale));
|
| 152 |
+
|
| 153 |
+
// Domains can override the mode
|
| 154 |
+
const mode = domain?.localePrefix || routingMode;
|
| 155 |
+
if (mode === 'always') {
|
| 156 |
+
shouldPrefix = true;
|
| 157 |
+
} else if (mode === 'as-needed') {
|
| 158 |
+
shouldPrefix = domain ? locale !== domain.defaultLocale : locale !== routing.defaultLocale;
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
return shouldPrefix ? prefixPathname(getLocalePrefix(locale, routing.localePrefix), pathname) : pathname;
|
| 162 |
+
}
|
| 163 |
+
function validateReceivedConfig(config) {
|
| 164 |
+
if (config.localePrefix?.mode === 'as-needed' && !('defaultLocale' in config)) {
|
| 165 |
+
throw new Error("`localePrefix: 'as-needed' requires a `defaultLocale`.");
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
export { applyPathnamePrefix, compileLocalizedPathname, getBasePath, getRoute, normalizeNameOrNameWithParams, serializeSearchParams, validateReceivedConfig };
|
e-commerce/node_modules/next-intl/dist/esm/development/plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export { default } from './plugin/createNextIntlPlugin.js';
|
e-commerce/node_modules/next-intl/dist/esm/development/plugin/config.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Avoid rollup's `replace` plugin to compile this away
|
| 2 |
+
const nodeEnvKey = 'NODE_ENV'.trim();
|
| 3 |
+
|
| 4 |
+
// We avoid reading `argv.includes('dev')` related to
|
| 5 |
+
// https://github.com/amannn/next-intl/issues/2006
|
| 6 |
+
const isDevelopment = process.env[nodeEnvKey] === 'development';
|
| 7 |
+
const isNextBuild = process.argv.includes('build');
|
| 8 |
+
const isDevelopmentOrNextBuild = isDevelopment || isNextBuild;
|
| 9 |
+
|
| 10 |
+
export { isDevelopment, isDevelopmentOrNextBuild, isNextBuild };
|
e-commerce/node_modules/next-intl/dist/esm/development/plugin/createNextIntlPlugin.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import normalizeExtractorConfig from '../extractor/normalizeExtractorConfig.js';
|
| 2 |
+
import initExtractionCompiler from './extractor/initExtractionCompiler.js';
|
| 3 |
+
import getNextConfig from './getNextConfig.js';
|
| 4 |
+
import { warn } from './utils.js';
|
| 5 |
+
import createMessagesDeclaration from './declaration/createMessagesDeclaration.js';
|
| 6 |
+
|
| 7 |
+
function initPlugin(pluginConfig, nextConfig) {
|
| 8 |
+
if (nextConfig?.i18n != null) {
|
| 9 |
+
warn("An `i18n` property was found in your Next.js config. This likely causes conflicts and should therefore be removed if you use the App Router.\n\nIf you're in progress of migrating from the Pages Router, you can refer to this example: https://next-intl.dev/examples#app-router-migration\n");
|
| 10 |
+
}
|
| 11 |
+
const skipWatchers = isNextTelemetryDetachedFlushProcess();
|
| 12 |
+
const messagesPathOrPaths = pluginConfig.experimental?.createMessagesDeclaration;
|
| 13 |
+
if (messagesPathOrPaths && !skipWatchers) {
|
| 14 |
+
createMessagesDeclaration(typeof messagesPathOrPaths === 'string' ? [messagesPathOrPaths] : messagesPathOrPaths);
|
| 15 |
+
}
|
| 16 |
+
let extractorConfig;
|
| 17 |
+
const experimental = pluginConfig.experimental;
|
| 18 |
+
const extract = experimental?.extract;
|
| 19 |
+
if (extract) {
|
| 20 |
+
extractorConfig = normalizeExtractorConfig({
|
| 21 |
+
extract,
|
| 22 |
+
messages: experimental.messages,
|
| 23 |
+
srcPath: experimental.srcPath
|
| 24 |
+
});
|
| 25 |
+
}
|
| 26 |
+
if (!skipWatchers) {
|
| 27 |
+
initExtractionCompiler(extractorConfig);
|
| 28 |
+
}
|
| 29 |
+
return getNextConfig(pluginConfig, nextConfig, extractorConfig);
|
| 30 |
+
}
|
| 31 |
+
function createNextIntlPlugin(i18nPathOrConfig = {}) {
|
| 32 |
+
const config = typeof i18nPathOrConfig === 'string' ? {
|
| 33 |
+
requestConfig: i18nPathOrConfig
|
| 34 |
+
} : i18nPathOrConfig;
|
| 35 |
+
return function withNextIntl(nextConfig) {
|
| 36 |
+
return initPlugin(config, nextConfig);
|
| 37 |
+
};
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* Next runs `telemetry/detached-flush.js` in a detached process to flush telemetry
|
| 42 |
+
* (often when `next dev` exits). That loads dev `next.config` with inherited
|
| 43 |
+
* `NODE_ENV=development`, which would otherwise start orphan plugin watchers.
|
| 44 |
+
*/
|
| 45 |
+
function isNextTelemetryDetachedFlushProcess() {
|
| 46 |
+
const scriptPath = process.argv[1];
|
| 47 |
+
if (!scriptPath) return false;
|
| 48 |
+
const normalized = scriptPath.replace(/\\/g, '/');
|
| 49 |
+
return normalized.includes('/telemetry/detached-flush');
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
export { createNextIntlPlugin as default };
|