Merge branch 'master' into master

This commit is contained in:
ecarjat
2025-06-06 09:22:44 +02:00
committed by GitHub
65 changed files with 3854 additions and 1975 deletions

View File

@@ -11,8 +11,7 @@ on:
- master
env:
REGISTRY: ghcr.io
IMAGE_NAME: donkie/spoolman
GHCR_IMAGE: donkie/spoolman
jobs:
#
@@ -123,7 +122,7 @@ jobs:
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
@@ -132,10 +131,10 @@ jobs:
uses: docker/build-push-action@v6.7.0
with:
context: .
tags: ${{ env.IMAGE_NAME }}:test
tags: ${{ env.GHCR_IMAGE }}:test
outputs: type=docker,dest=/tmp/spoolman.tar
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-amd64
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-amd64,mode=max
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64,mode=max
# Separate build for pull requests since PRs dont have access to store cache
- name: Build Spoolman (PR)
@@ -143,9 +142,9 @@ jobs:
uses: docker/build-push-action@v6.7.0
with:
context: .
tags: ${{ env.IMAGE_NAME }}:test
tags: ${{ env.GHCR_IMAGE }}:test
outputs: type=docker,dest=/tmp/spoolman.tar
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-amd64
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64
- name: Upload artifact
uses: actions/upload-artifact@v4.4.0
@@ -167,7 +166,7 @@ jobs:
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
@@ -176,10 +175,10 @@ jobs:
uses: docker/build-push-action@v6.7.0
with:
context: ./tests_integration
tags: ${{ env.IMAGE_NAME }}-tester:latest
tags: ${{ env.GHCR_IMAGE }}-tester:latest
outputs: type=docker,dest=/tmp/spoolman-tester.tar
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-tester
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-tester,mode=max
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-tester
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-tester,mode=max
# Separate build for pull requests since PRs dont have access to store cache
- name: Build Spoolman Tester (PR)
@@ -187,9 +186,9 @@ jobs:
uses: docker/build-push-action@v6.7.0
with:
context: ./tests_integration
tags: ${{ env.IMAGE_NAME }}-tester:latest
tags: ${{ env.GHCR_IMAGE }}-tester:latest
outputs: type=docker,dest=/tmp/spoolman-tester.tar
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-tester
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-tester
- name: Upload artifact
uses: actions/upload-artifact@v4.4.0
@@ -254,7 +253,7 @@ jobs:
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
@@ -263,8 +262,8 @@ jobs:
with:
context: .
platforms: linux/arm64
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-arm64
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-arm64,mode=max
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-arm64
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-arm64,mode=max
#
# Build arm/v7 image
# Don't run this for pull requests
@@ -293,7 +292,7 @@ jobs:
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
@@ -302,10 +301,10 @@ jobs:
with:
context: .
platforms: linux/arm/v7
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-armv7
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-armv7,mode=max
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-armv7
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-armv7,mode=max
#
# Release image if tests pass
# Release images GHCR and Docker Hub if tests pass
# Don't run this for pull requests
#
publish-images:
@@ -331,22 +330,41 @@ jobs:
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
- name: Extract metadata (tags, labels) for GHCR
id: meta-ghcr
uses: docker/metadata-action@v5.5.1
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
ghcr.io/${{ env.GHCR_IMAGE }}
tags: |
type=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Extract metadata (tags, labels) for Docker Hub
id: meta-dh
uses: docker/metadata-action@v5.5.1
with:
images: |
${{ vars.REMOTE_REGISTRY_URL }}/${{ vars.REMOTE_REGISTRY_NAMESPACE }}/${{ vars.REMOTE_REGISTRY_REPOSITORY }}
tags: |
type=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Log in to DockerHub
uses: docker/login-action@v3.3.0
with:
registry: ${{ vars.REMOTE_REGISTRY_URL }}
username: ${{ secrets.REMOTE_REGISTRY_USERNAME }}
password: ${{ secrets.REMOTE_REGISTRY_PASSWORD }}
- name: Store git commit and build date
run: |
echo "GIT_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
@@ -358,15 +376,17 @@ jobs:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
tags: |
${{ steps.meta-ghcr.outputs.tags }}
${{ steps.meta-dh.outputs.tags }}
labels: ${{ steps.meta-ghcr.outputs.labels }}
build-args: |
GIT_COMMIT
BUILD_DATE
cache-from: |
type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-amd64
type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-arm64
type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-armv7
type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64
type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-arm64
type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-armv7
#
# Create Github Release if CI was triggered by a tag
#

View File

@@ -2,23 +2,23 @@ default_language_version:
python: python3.9
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v5.0.0
hooks:
- id: check-yaml
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 24.4.2
rev: 25.1.0
hooks:
- id: black
args: ["--check"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
rev: v0.11.0
hooks:
- id: ruff
args: ["--target-version", "py39"]
- repo: https://github.com/pdm-project/pdm
rev: 2.17.3
rev: 2.22.4
hooks:
- id: pdm-lock-check

View File

@@ -1,4 +1,4 @@
FROM python:3.12-bookworm as python-builder
FROM python:3.12-bookworm AS python-builder
# Install dependencies
RUN apt-get update && apt-get install -y \
@@ -32,7 +32,7 @@ COPY --chown=app:app spoolman /home/app/spoolman/spoolman
COPY --chown=app:app alembic.ini /home/app/spoolman/
COPY --chown=app:app README.md /home/app/spoolman/
FROM python:3.12-bookworm as python-runner
FROM python:3.12-bookworm AS python-runner
LABEL org.opencontainers.image.source=https://github.com/Donkie/Spoolman
LABEL org.opencontainers.image.description="Keep track of your inventory of 3D-printer filament spools."
@@ -74,7 +74,6 @@ RUN chmod +x /home/app/spoolman/entrypoint.sh
WORKDIR /home/app/spoolman
ENV PATH="/home/app/spoolman/.venv/bin:${PATH}"
ENV PYTHONPATH="/home/app/spoolman:${PYTHONPATH}"
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown

2048
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +1,67 @@
{
"name": "spoolman-ui",
"version": "0.21.0",
"version": "0.22.1",
"engines": {
"node": "20.x"
},
"private": true,
"type": "module",
"dependencies": {
"@ant-design/icons": "^5.5.1",
"@ant-design/icons": "^5.6.1",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@loadable/component": "^5.16.4",
"@refinedev/antd": "^5.44.0",
"@refinedev/core": "^4.56.0",
"@refinedev/kbar": "^1.3.12",
"@refinedev/react-router-v6": "^4.6.0",
"@refinedev/simple-rest": "^5.0.8",
"@refinedev/antd": "^5.46.1",
"@refinedev/core": "^4.57.9",
"@refinedev/kbar": "^1.3.16",
"@refinedev/react-router": "^1.0.1",
"@refinedev/simple-rest": "^5.0.10",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query-devtools": "^4.36.1",
"@types/loadable__component": "^5.13.9",
"@types/lodash": "^4.17.13",
"@types/lodash": "^4.17.16",
"@types/uuid": "^10.0.0",
"@yudiel/react-qr-scanner": "^1.2.10",
"antd": "^5.22.1",
"axios": "^1.7.7",
"flag-icons": "^7.2.3",
"html-to-image": "^1.11.11",
"i18next": "^23.16.6",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.2",
"antd": "^5.24.6",
"axios": "^1.8.4",
"html-to-image": "^1.11.13",
"i18next": "^24.2.3",
"i18next-browser-languagedetector": "^8.0.4",
"i18next-http-backend": "^3.0.2",
"lodash": "^4.17.21",
"react": "^18.3.1",
"react": "^19.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.1.1",
"react-router-dom": "^6.28.0",
"react-to-print": "^2.15.1",
"uuid": "^11.0.3",
"react-dom": "^19.1.0",
"react-i18next": "^15.4.1",
"react-router": "^7.5.0",
"react-to-print": "^3.0.6",
"uuid": "^11.1.0",
"vite-plugin-svgr": "^4.3.0",
"zustand": "^5.0.1"
"zustand": "^5.0.3"
},
"devDependencies": {
"@refinedev/cli": "^2.16.39",
"@refinedev/cli": "^2.16.46",
"@simbathesailor/use-what-changed": "^2.0.0",
"@types/node": "^22.9.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.8.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.9",
"typescript": "^5.6.3",
"vite": "^5.4.11",
"vite-plugin-mkcert": "^1.17.6"
"@types/node": "^22.14.1",
"@types/react": "^19.1.1",
"@types/react-dom": "^19.1.2",
"@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.24.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"typescript": "^5.8.3",
"vite": "^6.2.6",
"vite-plugin-mkcert": "^1.17.8"
},
"scripts": {
"dev": "refine dev",
"build": "tsc && refine build",
"build.zip": "cd ./dist && zip -r spoolman-client.zip ./ ./ && cd ..",
"preview": "refine start",
"refine": "refine"
"refine": "refine",
"check-i18n": "node scripts/check-i18n.js"
},
"browserslist": {
"production": [

View File

@@ -393,6 +393,10 @@
"base_url": {
"label": "Základní URL",
"tooltip": "Základní adresa URL, která se má použít při generování funkcí, jako jsou QR kódy."
},
"round_prices": {
"label": "Zaokrouhlené ceny",
"tooltip": "Zaokrouhlete ceny na nejbližší celé číslo."
}
},
"settings": "Nastavení"

View File

@@ -124,13 +124,13 @@
"withIcon": "Mit Icon"
},
"template": "Beschriftung Vorlagen",
"templateHelp": "Benutze {} um Texte der Spule dynamisch zu übernehmen. Zum Beispiel wird {id} durch die ID der Spule ersetzt, {filament.material} wird durch das Material der Spule ersetzt. Texte zwischen zwei Sternen ** werden fett dargestellt. Drücke auf den Knopf um alle verfügbaren Variablen zu sehen.",
"templateHelp": "Benutze {} um Texte der Spule dynamisch zu übernehmen. Zum Beispiel wird {id} durch die ID der Spule ersetzt, {filament.material} wird durch das Material der Spule ersetzt. Wenn ein Wert fehlt, wird er mit \"?\" ersetzt. Ein zweiter Satz von {} kann genutzt werden, um dieses zu umgehen. Zusätzlich wird jeglicher Text zwischen einem Satz von {} entfernt, wenn der Wert fehlt. Beispielsweise zeigt {Charge: {lot_nr}} nur das Label an, wenn eine Spule eine Charge definiert hat. Texte zwischen zwei Sternen ** werden fett dargestellt. Drücke auf den Knopf um alle verfügbaren Variablen zu sehen.",
"showQRCode": "Drucke QR Code",
"useHTTPUrl": {
"label": "QR Code Link",
"tooltip": "Verwendet einen proprietären Link, der nur funktioniert, wenn er mit der (standart) Scanfunktion von Spoolman gescannt wird. Die URL verwendet entweder die in den Einstellungen angegebene Basis-URL oder die aktuelle Seiten-URL, wenn diese nicht festgelegt ist.",
"tooltip": "Die Standardeinstellung verwendet einen proprietären Link, der nur funktioniert, wenn er mit der Scanfunktion von Spoolman gescannt wird. URL verwendet entweder die in den Einstellungen angegebene Basis-URL oder wenn diese nicht festgelegt ist die aktuelle Seiten-URL.",
"options": {
"default": "Standart",
"default": "Standard",
"url": "URL"
},
"preview": "Vorschau:"
@@ -185,7 +185,7 @@
"filament_external": "Extern"
},
"fields_help": {
"weight_to_use": "Auswählen, welches Gewicht verwendet werden soll. Das gemessenes Gewicht ist nur verfügbar, wenn das Gewicht der Spule für das Filament hinterlegt ist.",
"weight_to_use": "Auswählen, welches Gewicht verwendet werden soll. Das gemessene Gewicht ist nur verfügbar, wenn das Gewicht der Spule für das Filament hinterlegt ist.",
"used_weight": "Wie viel Gewicht bereits verbraucht wurde z.B. 0g für eine neue Spule.",
"remaining_weight": "Wie viel Filament noch auf der Spule ist. Entspricht dem auf der Spule angegeben Nettogewicht, wenn die Spule neu ist.",
"measured_weight": "Gesamtgewicht von Spule und Filament.",
@@ -219,6 +219,9 @@
},
"adjust_filament_value": "Verbrauchte Menge",
"measurement_type_label": "Messungstyp"
},
"formats": {
"last_used": "Zuletzt genutzt {{date}}"
}
},
"filament": {
@@ -330,6 +333,9 @@
},
"help": {
"list": "Hilfe | Spoolman"
},
"locations": {
"list": "Orte | Spoolman"
}
},
"help": {
@@ -355,6 +361,10 @@
"base_url": {
"label": "Basis URL",
"tooltip": "Die basis URL für die Generierung von Funktionen wie QR Codes."
},
"round_prices": {
"label": "Gerundeter Preis",
"tooltip": "Rundet die Preise auf die nächsthöhere ganze Zahl."
}
},
"extra_fields": {
@@ -365,7 +375,7 @@
"unit": "Einheit",
"default_value": "Standardwert",
"order": "Reihenfolge",
"multi_choice": "mehrfach Auswahl",
"multi_choice": "Mehrfachauswahl",
"key": "Schlüssel",
"choices": "Auswahlmöglichkeiten"
},
@@ -389,5 +399,11 @@
"non_unique_key_error": "Der Schlüssel muss einzigartig sein."
},
"settings": "Einstellungen"
},
"locations": {
"no_location": "Kein Ort",
"no_locations_help": "Diese Seite lässt Sie Ihre Spulen zu Orten zuweisen, fügen Sie ein paar Spulen hinzu um loszulegen!",
"locations": "Orte",
"new_location": "Neuer Ort"
}
}

View File

@@ -316,6 +316,10 @@
"base_url": {
"label": "Base URL",
"tooltip": "The base URL to use when generating features such as QR codes."
},
"round_prices": {
"label": "Round prices",
"tooltip": "Round prices to the nearest whole number."
}
},
"extra_fields": {

View File

@@ -28,13 +28,39 @@
"filter": "Filtreeri",
"unArchive": "Võta arhiivist tagasi",
"hideArchived": "Peida arhiveeritud",
"showArchived": "Näita arhiveeritud"
"showArchived": "Näita arhiveeritud",
"continue": "Jätka"
},
"notifications": {
"error": "Viga (kood: {{statusCode}})",
"undoable": "Sul on {{seconds}} sekundit, et muudatus tagasi võtta.",
"success": "Õnnestunud",
"createSuccess": "Loodi {{resource}}"
"createSuccess": "Loodi {{resource}}",
"createError": "Tekkis viga {{resource}} loomisel. (staatuse kood: {{statusCode}})",
"deleteSuccess": "Edukalt kustutatud {{resource}}",
"deleteError": "Viga {{resource}} kustutamisel. (staatuse kood: {{statusCode}})",
"editSuccess": "Edukalt muudetud {{resource}}",
"editError": "Viga {{resource}} muutmisel (staatuse kood: {{statusCode}})",
"importProgress": "Importimine: {{processed}}/{{total}}",
"saveSuccessful": "Salvesta edukalt!",
"validationError": "Viga validerimisel: {{error}}"
},
"warnWhenUnsavedChanges": "Oled sa kindel, et soovid lahkuda? Sul on salvestamata muudatusi."
"warnWhenUnsavedChanges": "Oled sa kindel, et soovid lahkuda? Sul on salvestamata muudatusi.",
"kofi": "Anna mulle tippi Ko-fi's",
"loading": "Laadimine",
"version": "Versioon",
"unknown": "Tundmatu",
"yes": "Jah",
"no": "Ei",
"tags": {
"clone": "Klooni"
},
"dashboard": {
"title": "Juhtpaneel"
},
"printing": {
"generic": {
"title": "Trükkib"
}
}
}

View File

@@ -74,7 +74,7 @@
"deleteSettingsConfirm": "Êtes-vous sûr de vouloir supprimer ce préréglage?",
"settingsName": "Nom du préréglage",
"saveAsImage": "Enregistrer comme image",
"itemCopies": "Nombre d'article",
"itemCopies": "Nombre d'articles",
"duplicateSettings": "Dupliquer",
"saveSetting": "Enregistrer les préréglages"
},
@@ -400,6 +400,7 @@
"locations": {
"locations": "Emplacements",
"new_location": "Créer un emplacement",
"no_location": "Pas de localisation"
"no_location": "Pas de localisation",
"no_locations_help": "Cette page vous permet d'organiser vos bobines par lieu, ajoutez une bobine pour commencer!"
}
}

View File

@@ -0,0 +1,396 @@
{
"notifications": {
"undoable": "取り消しまで残り{{seconds}}秒",
"editSuccess": "{{resource}}の編集が完了",
"success": "完了",
"error": "エラー (status code: {{statusCode}})",
"createSuccess": "{{resource}}の作成が完了",
"deleteSuccess": "{{resource}}の削除が完了",
"editError": "{{resource}}の編集中にエラーが発生しました (status code: {{statusCode}})",
"importProgress": "インポート中: {{processed}}/{{total}}",
"saveSuccessful": "保存完了!",
"createError": "{{resource}}を作成中にエラーが発生しました。 (status code: {{statusCode}})",
"validationError": "検証エラー: {{error}}",
"deleteError": "{{resource}}を削除中にエラーが発生しました (status code: {{statusCode}})"
},
"actions": {
"create": "作成",
"edit": "編集",
"clone": "複製",
"list": "リスト",
"show": "表示"
},
"buttons": {
"archive": "アーカイブ",
"saveAndAdd": "保存して追加",
"create": "作成",
"edit": "編集",
"cancel": "キャンセル",
"confirm": "本当によろしいですか?",
"continue": "続ける",
"filter": "フィルター",
"clear": "クリア",
"refresh": "再読込",
"show": "表示",
"undo": "元に戻す",
"hideArchived": "アーカイブを隠す",
"showArchived": "アーカイブを表示",
"unArchive": "非アーカイブ",
"hideColumns": "コラムを隠す",
"notAccessTitle": "アクセス権限がありません",
"save": "保存",
"logout": "ログアウト",
"delete": "削除",
"clone": "複製",
"clearFilters": "フィルタをクリア",
"import": "インポート"
},
"printing": {
"generic": {
"verticalSpacing": "垂直方向の余白",
"printerMarginTop": "上側のセーフゾーン",
"deleteSettings": "現在のプリセットを削除",
"itemCopies": "複製",
"print": "印刷",
"paperSize": "用紙サイズ",
"customSize": "カスタム",
"showBorder": "線を表示",
"previewScale": "スケールを表示",
"skipItems": "スキップ",
"horizontalSpacing": "水平方向の余白",
"marginLeft": "左側の余白",
"marginBottom": "下側の余白",
"printerMarginBottom": "下側のセーフゾーン",
"borders": {
"none": "なし",
"grid": "グリッド",
"border": "線"
},
"settingsName": "プリセット名",
"saveSetting": "プリセットを保存",
"saveAsImage": "画像として保存",
"columns": "列",
"marginRight": "右側の余白",
"defaultSettings": "デフォルト",
"title": "プリント中",
"description": "以下の設定を調整して、希望する印刷レイアウトを得てください。プリンターやOSが独自のマージンや拡大縮小を適用する場合があるため、すべてが正確になるまでには試行錯誤が必要かもしれません。実際のラベルに印刷する前に、普通の紙でテストしてください。",
"helpMargin": "マージンはラベル用紙とプリンターに合わせて設定する必要があります。これらを変更すると、グリッド全体のサイズに影響します。",
"settings": "プリセット",
"helpPrinterMargin": "セーフゾーンは、プリンターが用紙の端にどれだけ近づけて印刷できるかの設定です。これらを変更しても、グリッド全体には影響しません。",
"dimensions": "寸法",
"duplicateSettings": "複製",
"contentSettings": "コンテンツ設定",
"marginTop": "上側の余白",
"printerMarginRight": "右側のセーフゾーン",
"layoutSettings": "レイアウト設定",
"addSettings": "新しいプリセットを追加",
"deleteSettingsConfirm": "本当にこのプリセットを削除しますか?",
"newSetting": "新規",
"rows": "行",
"printerMarginLeft": "左側のセーフゾーン"
},
"qrcode": {
"button": "ラベルを印刷",
"title": "ラベル印刷",
"template": "ラベルテンプレート",
"textSize": "ラベルのテキストサイズ",
"useHTTPUrl": {
"options": {
"default": "デフォルト",
"url": "URL"
},
"preview": "プレビュー:",
"label": "QRコードリンク",
"tooltip": "Spoolmanのスキャン機能からスキャンした場合にのみ機能する独自リンクを使用しますデフォルト。URLは設定で指定されたベースURLを使用するか、設定されていない場合は現在のページURLを使用します。"
},
"showQRCode": "QRコード印刷",
"showContent": "ラベル印刷",
"templateHelp": "値をテキストとして挿入するには{}を使用します。例えば、{id}はスプールIDに置き換えられ、{filament.material}はスプールの材質に置き換えられます。値が欠けている場合は「?」に置き換えられます。二重の{}を使用して、この置換を削除することができます。{}のセット間のテキストは、値が欠けている場合に削除されます。例えば、{Lot Nr: {lot_nr}}は、スプールにロット番号がある場合にのみラベルを表示します。テキストを太字にするには、二重のアスタリスク**で囲みます。利用可能なすべてのタグのリストを表示するには、ボタンをクリックしてください。",
"showQRCodeMode": {
"no": "いいえ",
"withIcon": "アイコン付き",
"simple": "シンプル"
}
},
"spoolSelect": {
"description": "スプールのラベル印刷対象を選択してください。",
"showArchived": "アーカイブを表示",
"selectAll": "すべてを選択 / 選択解除",
"title": "スプールを選択",
"selectedTotal_other": "{{count}}本のスプールを選択",
"noSpoolsSelected": "スプールが選択されていません。"
}
},
"warnWhenUnsavedChanges": "本当に離れますか? 編集が保存されていません。",
"kofi": "Ko-fiでチップをください",
"unknown": "未知",
"version": "バージョン",
"loading": "読み込み中",
"no": "いいえ",
"dashboard": {
"title": "ダッシュボード"
},
"yes": "はい",
"tags": {
"clone": "複製"
},
"scanner": {
"description": "スプールの詳細を表示するには、SpoolmanのQRコードをスキャンしてください。",
"error": {
"notReadable": "カメラを読み込むことができません。",
"notFound": "カメラが見つかりません。",
"unknown": "未知のエラーが発生しました。({{error}})",
"insecureContext": "このページはHTTPSで接続されていません。",
"streamApiNotSupported": "ブラウザがMediaStream APIに対応していません。",
"notAllowed": "カメラへのアクセスが許可されていません。"
},
"title": "QRコードスキャナ"
},
"spool": {
"fields": {
"filament_internal": "内部",
"filament_external": "外部",
"price": "価格",
"remaining_weight": "残りの重さ",
"measured_weight": "計測した重さ",
"lot_nr": "ロットナンバー",
"first_used": "最初の使用",
"last_used": "最後の使用",
"comment": "コメント",
"material": "マテリアル",
"weight_to_use": "重さ",
"registered": "登録",
"archived": "アーカイブ",
"used_length": "使用した長さ",
"initial_weight": "最初の重さ",
"spool_weight": "空の重さ",
"location": "場所",
"filament": "フィラメント",
"used_weight": "使用した重さ",
"remaining_length": "残りの長さ",
"id": "ID",
"filament_name": "フィラメント"
},
"fields_help": {
"price": "スプール全体の価格。設定されていない場合は、フィラメントの価格が代わりに使用されます。",
"measured_weight": "フィラメントとスプールの重量。",
"location": "スプールを複数の場所に保管している場合、そのスプールの保管場所。",
"used_weight": "スプールからどれだけのフィラメントが使用されたか。新しいスプールは0gとなるはずです。",
"weight_to_use": "入力する重さを選択してください。測定重量は、選択したフィラメントにスプール重量が設定されている場合のみ利用可能です。",
"remaining_weight": "スプールに残っているフィラメントの量。新しいスプールの場合、これはスプールの重量と一致するはずです。",
"initial_weight": "スプール上のフィラメントの初期重量(正味重量)。設定されていない場合は、フィラメントオブジェクトからの重量が使用されます。",
"lot_nr": "メーカーのロット番号。複数のスプールを使用する場合、印刷の色が一定であることを確認するために使用できます。",
"external_filament": "外部データベースからフィラメントを選択しました。このスプールを作成する際に、フィラメントオブジェクト(場合によってはメーカーオブジェクトも)が自動的に作成されます。すでにこのフィラメントのフィラメントオブジェクトを作成している場合、重複したフィラメントオブジェクトが作成される可能性があります。",
"spool_weight": "空の時のスプールの重量。フィラメントまたはメーカーの値を使用する場合は空のままにしてください。"
},
"titles": {
"create": "スプールを作成",
"clone": "スプールを複製",
"show_title": "[スプール #{{id}}] {{name}}",
"adjust": "スプールフィラメントの調整",
"edit": "スプールを編集",
"archive": "アーカイブしたスプール",
"list": "スプール",
"show": "スプールを表示"
},
"form": {
"measurement_type_label": "計測タイプ",
"new_location_prompt": "新しい場所を入力",
"measurement_type": {
"length": "長さ",
"weight": "重さ"
},
"adjust_filament_help": "ここではスプールに直接フィラメントを追加したり差し引いたりすることができます。正の値はフィラメントを消費し、負の値はフィラメントを追加します。",
"spool_updated": "このスプールは、このページを開いてから他の誰か/何かによって更新されています。保存すると、それらの変更が上書きされます!",
"adjust_filament_value": "消費量"
},
"messages": {
"archive": "本当にこのスプールをアーカイブしますか?"
},
"formats": {
"last_used": "最終使用日: {{date}}"
},
"spool": "スプール"
},
"filament": {
"fields": {
"id": "ID",
"name": "名称",
"material": "マテリアル",
"price": "価格",
"density": "密度",
"spool_weight": "スプールの重さ",
"registered": "登録",
"comment": "コメント",
"color_hex": "色",
"single_color": "単色",
"multi_color": "複数色",
"longitudinal": "縦方向",
"article_number": "製品番号",
"coaxial": "断面",
"external_id": "外部ID",
"vendor": "製造者",
"diameter": "直径",
"settings_extruder_temp": "エクストルーダー温度",
"settings_bed_temp": "ベッド温度",
"weight": "重さ",
"vendor_name": "製造者",
"spools": "スプールを表示"
},
"filament": "フィラメント",
"fields_help": {
"name": "フィラメント名: 同じメーカーの他のフィラメントと区別するための名称です。例えば色を含めると良いでしょう。",
"article_number": "例: EAN, UPC, etc.",
"spool_weight": "空のスプールの重量。スプールの測定重量を決定するために使用されます。",
"material": "例: PLA, ABS, PETG, etc.",
"multi_color_direction": "フィラメントは2つの方法で複数の色を持つことができます。断面: 押出し時に円の断面に複数色を持つ二色フィラメントのような形態です。縦方向: 長手方向の色変化によるもので、スプールに沿って色が変化するグラデーションフィラメントのような形態です。",
"price": "スプール全体の価格。",
"weight": "フィラメントの正味重量(フルスプール時)。スプール自体の重量は含まず、フィラメントのみの重量です。通常、パッケージに記載されている重量です。"
},
"titles": {
"create": "フィラメントを作成",
"clone": "フィラメントを複製",
"list": "フィラメント",
"edit": "フィラメントを編集",
"show_title": "[フィラメント #{{id}}] {{name}}",
"show": "フィラメントを表示"
},
"form": {
"filament_updated": "このフィラメントは、このページを開いてから他の誰か/何かによって更新されています。保存すると、それらの変更が上書きされます!",
"import_external": "外部からインポート",
"import_external_description": "リストからフィラメントを選択すると、フィラメント作成フォームに詳細情報が自動的に入力されます。これにより、フォームに入力したデータは上書きされます。<br><br>「OK」をクリックすると、メーカーオブジェクトが自動的に作成されます。"
},
"buttons": {
"add_spool": "スプールを追加"
}
},
"vendor": {
"fields": {
"id": "ID",
"registered": "登録",
"external_id": "外部ID",
"name": "名称",
"empty_spool_weight": "空スプールの重さ",
"comment": "コメント"
},
"fields_help": {
"empty_spool_weight": "この製造者の空スプールの重量。"
},
"titles": {
"create": "製造者を作成",
"list": "製造者",
"show": "製造者を表示",
"clone": "製造者を複製",
"edit": "製造者を編集",
"show_title": "[製造者 #{{id}}] {{name}}"
},
"form": {
"vendor_updated": "この製造者は、このページを開いてから他の誰か/何かによって更新されています。保存すると、それらの変更が上書きされます!"
},
"vendor": "製造者"
},
"help": {
"help": "ヘルプ",
"resources": {
"filament": "フィラメントのブランド。名前、素材、色、直径などの特性を持っています。",
"spool": "特定のフィラメントの個々の物理的なスプール。",
"vendor": "フィラメントを製造している会社。"
},
"description": "<title>ヘルプ</title><p>使い始めるためのヒントをいくつか紹介します。</p><p>Spoolmanは3種類のデータを管理しています</p><itemsHelp/><p>データベースに新しいスプールを追加するには、まず<filamentCreateLink>フィラメント</filamentCreateLink>を作成することから始めます。それが完了したら、<spoolCreateLink>スプール</spoolCreateLink>を作成します。後日、同じフィラメントの追加スプールを入手した場合は、追加のスプールを生成し、同じフィラメントを再利用するだけです。</p><p>必要に応じて、フィラメントを製造している<vendorCreateLink>製造者</vendorCreateLink>を作成して、その情報を追跡することもできます。</p><p>Moonrakerなどの他の3DプリンターサービスをSpoolmanにリンクすることもでき、フィラメントの使用状況を自動的に監視し、スプールを更新することができます。設定方法については<readmeLink>Spoolman README</readmeLink>を参照してください。</p>"
},
"table": {
"actions": "アクション"
},
"settings": {
"settings": "設定",
"header": "設定",
"general": {
"tab": "一般",
"currency": {
"label": "通過"
},
"base_url": {
"label": "ベースURL",
"tooltip": "QRコードなどの機能を生成する際に使用する基本URL。"
},
"round_prices": {
"label": "価格の端数処理",
"tooltip": "価格の小数点以下を四捨五入する。"
}
},
"extra_fields": {
"tab": "追加項目",
"params": {
"unit": "単位",
"order": "数量",
"default_value": "デフォルト値",
"choices": "選択肢",
"multi_choice": "複数選択",
"name": "名称",
"key": "キー",
"field_type": "タイプ"
},
"field_type": {
"float": "小数点型",
"float_range": "小数点型の範囲",
"datetime": "日時",
"boolean": "真偽値",
"choice": "選択肢",
"integer_range": "数値範囲",
"text": "テキスト",
"integer": "整数"
},
"boolean_true": "はい",
"key_not_changed": "別のキーに変更してください。",
"boolean_false": "いいえ",
"delete_confirm": "{{name}}のフィールドを削除しますか?",
"delete_confirm_description": "フィールドとすべてのエンティティの関連データがすべて削除されます。",
"description": "<p>ここで追加のカスタムフィールドを追加できます。</p><p>フィールドを追加すると、そのキーやタイプを変更することはできません。また、選択肢タイプのフィールドでは、選択肢の削除や複数選択の状態を変更することはできません。フィールドを削除すると、すべてのエンティティの関連データが削除されます。</p><p>キーは他のプログラムがデータを読み書きする際に使用するものなので、カスタムフィールドがサードパーティのプログラムと連携する予定がある場合は、正しく設定してください。デフォルト値は新しいアイテムにのみ適用されます。</p><p>追加フィールドはテーブルビューで並べ替えやフィルタリングすることはできません。</p>",
"choices_missing_error": "選択肢を削除できません。下記の選択肢がありません: {{choices}}",
"non_unique_key_error": "キーは一意の値である必要があります。"
}
},
"documentTitle": {
"home": {
"list": "ホーム | Spoolman"
},
"help": {
"list": "ヘルプ | Spoolman"
},
"default": "Spoolman",
"suffix": " | Spoolman",
"filament": {
"edit": "フィラメントの編集 #{{id}} | Spoolman",
"clone": "フィラメントの複製 #{{id}} | Spoolman",
"show": "フィラメント #{{id}} | Spoolman",
"create": "フィラメントの作成 | Spoolman",
"list": "フィラメント | Spoolman"
},
"spool": {
"list": "スプール | Spoolman",
"show": "スプール #{{id}} | Spoolman",
"create": "スプールの作成 | Spoolman",
"edit": "スプールの編集 #{{id}} | Spoolman",
"clone": "スプールの複製 #{{id}} | Spoolman"
},
"vendor": {
"edit": "製造者の編集 #{{id}} | Spoolman",
"clone": "製造者の複製 #{{id}} | Spoolman",
"show": "製造者 #{{id}} | Spoolman",
"create": "製造者の作成 | Spoolman",
"list": "製造者 | Spoolman"
},
"locations": {
"list": "場所 | Spoolman"
}
},
"locations": {
"locations": "場所",
"new_location": "新しい場所",
"no_location": "場所なし",
"no_locations_help": "このページでは、スプールを場所ごとに整理することができます!"
},
"home": {
"home": "ホーム"
}
}

View File

@@ -361,6 +361,10 @@
"base_url": {
"label": "Basis-URL",
"tooltip": "De basis-URL die moet worden gebruikt bij het genereren van functies zoals QR-codes."
},
"round_prices": {
"label": "Afgeronde prijzen",
"tooltip": "Rond prijzen af op het dichtstbijzijnde gehele getal."
}
},
"extra_fields": {

View File

@@ -39,7 +39,8 @@
"create": "Tworzenie szpuli",
"clone": "Klonowanie szpuli",
"archive": "Zarchiwizuj szpulę",
"show_title": "[Szpula #{{id}}] {{name}}"
"show_title": "[Szpula #{{id}}] {{name}}",
"adjust": "Dostosuj szpulę filamentu"
},
"spool": "Szpule",
"fields": {
@@ -89,7 +90,8 @@
"weight": "Waga"
},
"measurement_type_label": "Typ pomiaru",
"adjust_filament_help": "Tutaj możesz bezpośrednio dodać lub usunąć filament z szpuli. Dodatnia wartość spowoduje odjęcie filamentu, ujemna wartość spowoduje jego dodanie."
"adjust_filament_help": "Tutaj możesz bezpośrednio dodać lub usunąć filament z szpuli. Dodatnia wartość spowoduje odjęcie filamentu, ujemna wartość spowoduje jego dodanie.",
"adjust_filament_value": "Ilość zużycia"
},
"formats": {
"last_used": "Ostatnio użyty {{date}}"
@@ -155,18 +157,18 @@
"newSetting": "Nowe",
"defaultSettings": "Domyślne",
"itemCopies": "Skopiuj element",
"settings": "Ustawienia wstępne",
"settings": "Presety",
"saveAsImage": "Zapisz jako obraz",
"duplicateSettings": "Zduplikuj",
"deleteSettingsConfirm": "Czy jesteś pewny, że chcesz usunąć to ustawienie?",
"addSettings": "Dodaj nowe ustawienie wstępne",
"addSettings": "Dodaj nowe ustawienia",
"deleteSettings": "Usuń aktualne ustawienie",
"settingsName": "Nazwa ustawienia",
"saveSetting": "Zapisz ustawienie"
},
"qrcode": {
"button": "Drukuj etykiety",
"title": "Drukowanie etykiet",
"title": "Drukowanie etykiety",
"bedTemp": "TS: {{temp}}",
"extruderTemp": "TE: {{temp}}",
"textSize": "Rozmiar tekstu etykiety",
@@ -360,10 +362,14 @@
"base_url": {
"tooltip": "Bazowy URL zostanie użyty podczas generowania QR code oraz w innych funkcjonalnościach.",
"label": "Bazowy URL"
},
"round_prices": {
"tooltip": "Zaokrąglij ceny do najbliższej liczby całkowitej.",
"label": "Zaokrąglone ceny"
}
},
"extra_fields": {
"tab": "Dodatkowe Parametry",
"tab": "Dodatkowe parametry",
"boolean_true": "Tak",
"boolean_false": "Nie",
"params": {
@@ -371,7 +377,7 @@
"field_type": "Typ",
"unit": "Jednostka",
"order": "Kolejność",
"default_value": "Domyślna Wartość",
"default_value": "Domyślna wartość",
"key": "Klucz",
"choices": "Wybory",
"multi_choice": "Wielokrotny wybór"

View File

@@ -1 +1,398 @@
{}
{
"buttons": {
"saveAndAdd": "Salvar e Adicionar",
"save": "Salvar",
"continue": "Continuar",
"filter": "Filtrar",
"create": "Criar",
"logout": "Sair",
"delete": "Excluir",
"edit": "Editar",
"cancel": "Cancelar",
"clear": "Limpar",
"refresh": "Atualizar",
"show": "Mostrar",
"undo": "Desfazer",
"import": "Importar",
"clone": "Clonar",
"archive": "Arquivar",
"unArchive": "Desarquivar",
"hideArchived": "Ocultar Arquivados",
"showArchived": "Mostrar Arquivados",
"notAccessTitle": "Você não tem permissão para acessar",
"hideColumns": "Ocultar Colunas",
"clearFilters": "Limpar Filtros",
"confirm": "Você tem certeza?"
},
"tags": {
"clone": "Clonar"
},
"printing": {
"generic": {
"rows": "Linhas",
"paperSize": "Tamanho do Papel",
"showBorder": "Mostrar Borda",
"skipItems": "Pular Itens",
"borders": {
"grid": "Grade",
"none": "Nenhuma",
"border": "Borda"
},
"columns": "Colunas",
"customSize": "Personalizado",
"title": "Imprimindo",
"description": "Ajuste as configurações abaixo para obter o layout de impressão desejado. Tenha em mente que as impressoras e o seu sistema operacional podem aplicar suas próprias margens e dimensionamento, portanto, pode ser necessário realizar algumas tentativas e erros antes que tudo esteja correto. Teste em um papel comum antes de imprimir nas etiquetas reais.",
"helpMargin": "As margens devem ser configuradas para corresponder às suas etiqueta e impressora. Alterá-las afetará o tamanho de toda a grade.",
"helpPrinterMargin": "A Zona de Segurança deve ser definida para o quão próximo da borda do papel sua impressora pode imprimir. Alterá-la não afetará toda a grade.",
"print": "Imprimir",
"itemCopies": "Cópias do Item",
"contentSettings": "Configurações de Conteúdo",
"layoutSettings": "Configurações de Layout",
"horizontalSpacing": "Espaçamento Horizontal",
"verticalSpacing": "Espaçamento Vertical",
"marginLeft": "Margem Esquerda",
"marginRight": "Margem Direita",
"marginTop": "Margem Superior",
"marginBottom": "Margem Inferior",
"printerMarginLeft": "Zona de Segurança Esquerda",
"printerMarginRight": "Zona de Segurança Direita",
"printerMarginTop": "Zona de Segurança Superior",
"printerMarginBottom": "Zona de Segurança Inferior",
"settings": "Predefinições",
"defaultSettings": "Padrão",
"addSettings": "Adicionar Nova Predefinição",
"newSetting": "Novo",
"duplicateSettings": "Duplicar",
"deleteSettings": "Excluir Predefinição Atual",
"deleteSettingsConfirm": "Tem certeza de que deseja excluir esta predefinição?",
"settingsName": "Nome da Predefinição",
"saveSetting": "Salvar Predefinições",
"saveAsImage": "Salvar como Imagem",
"dimensions": "Dimensões",
"previewScale": "Escala da pré-visualização"
},
"spoolSelect": {
"selectedTotal_one": "{{count}} carretel selecionado",
"selectedTotal_many": "{{count}} carretéis selecionados",
"selectedTotal_other": "{{count}} carretéis selecionados",
"title": "Selecionar Carretéis",
"description": "Selecione os carretéis para imprimir suas etiquetas.",
"showArchived": "Mostrar Arquivado",
"noSpoolsSelected": "Você não selecionou algum dos carretéis.",
"selectAll": "Selecionar/Desselecionar Tudo"
},
"qrcode": {
"button": "Imprimir Etiquetas",
"title": "Impressão de Etiquetas",
"template": "Modelo de Etiqueta",
"textSize": "Tamanho do Texto da Etiqueta",
"showContent": "Imprimir Etiqueta",
"useHTTPUrl": {
"label": "Endereço do código QR",
"tooltip": "Usará um endereço proprietário que funcionará somente se digitalizado a partir do recurso de digitalização do Spoolman (padrão). O URL usa o URL Base, especificada nas Configurações, ou o URL da página atual se nenhuma estiver definida.",
"options": {
"default": "Padrão",
"url": "URL"
},
"preview": "Pré-visualização:"
},
"showQRCode": "Imprimir Código QR",
"showQRCodeMode": {
"no": "Não",
"simple": "Simples",
"withIcon": "Com Ícone"
},
"templateHelp": "Use \"{tag}\" para inserir valores do objeto carretel como texto. Por exemplo, {id} será substituído pelo valor do \"id\" do carretel ou {filament.material} será substituído pelo material do carretel. Se um valor estiver faltando, ele será substituído por \"?\". Um segundo conjunto de chaves, exemplo \"{{tag}}\", pode ser usado para remover isso. Além disso, qualquer texto entre os conjuntos de chaves será removido se o valor não existir. Por exemplo, {Lote Nº: {lot_nr}} só mostrará o rótulo se o carretel tiver um número de lote. Coloque o texto com asterisco duplo ** para colocar em negrito. Clique no botão ao lado para ver uma lista de todas as tags disponíveis."
}
},
"spool": {
"formats": {
"last_used": "Última utilização em {{date}}"
},
"fields": {
"id": "ID",
"material": "Material",
"weight_to_use": "Peso",
"used_weight": "Peso Utilizado",
"measured_weight": "Peso Medido",
"used_length": "Comprimento Usado",
"initial_weight": "Peso Inicial",
"filament": "Filamento",
"filament_internal": "Interno",
"filament_external": "Externo",
"price": "Preço",
"spool_weight": "Peso sem Filamento",
"location": "Localização",
"lot_nr": "Número do Lote",
"first_used": "Primeiro Uso",
"last_used": "Último Uso",
"registered": "Registrado",
"comment": "Comentário",
"archived": "Arquivado",
"remaining_length": "Comprimento Restante",
"remaining_weight": "Peso Restante",
"filament_name": "Filamento"
},
"fields_help": {
"price": "Preço de um carretel cheio. Se não for definido, o preço do filamento será assumido.",
"weight_to_use": "Selecione que tipo de valor de peso inserir. O \"Peso Medido\" só estará disponível se o peso do carretel estiver definido para o filamento selecionado.",
"used_weight": "Quanto filamento foi usado do carretel. Um novo carretel deve ter 0g usado.",
"remaining_weight": "Quanto filamento resta no carretel. Para um carretel novo, isso deve corresponder ao peso do carretel.",
"measured_weight": "Quanto pesa o filamento e o carretel.",
"initial_weight": "O peso inicial do filamento no carretel (peso líquido). Caso não seja especificado, será usado o peso da definição do filamento.",
"spool_weight": "O peso do carretel vazio. Deixe em branco para utilizar o valor do filamento ou do fabricante.",
"location": "Onde o carretel está localizado se você tiver vários locais onde armazena seus carretéis.",
"lot_nr": "Número de lote do fabricante. Pode ser usado para garantir que uma impressão tenha cores consistentes se vários carretéis forem usados.",
"external_filament": "Você selecionou um filamento do banco de dados externo. Um objeto de filamento (e possivelmente um objeto de fabricante) será criado automaticamente quando você criar este carretel. Isso pode criar objetos de filamento duplicados se você já tiver criado um objeto de filamento para esse filamento."
},
"titles": {
"create": "Criar Carretel",
"clone": "Clonar Carretel",
"edit": "Editar Carretel",
"list": "Carretéis",
"show": "Mostrar Carretel",
"show_title": "[Carretel #{{id}}] {{name}}",
"archive": "Arquivar Carretel",
"adjust": "Ajustar Filamento no Carretel"
},
"form": {
"measurement_type": {
"length": "Comprimento",
"weight": "Peso"
},
"measurement_type_label": "Tipo de Medida",
"adjust_filament_help": "Aqui você pode adicionar ou subtrair filamento diretamente do carretel. Um valor positivo consumirá filamento, um valor negativo adicionará filamento.",
"adjust_filament_value": "Quantidade Consumida",
"new_location_prompt": "Insira um novo local",
"spool_updated": "Este carretel foi atualizado por alguém/outra coisa desde que você abriu esta página. Salvar irá sobrescrever essas alterações!"
},
"messages": {
"archive": "Tem certeza de que deseja arquivar este carretel?"
},
"spool": "Carretéis"
},
"filament": {
"fields": {
"name": "Nome",
"material": "Material",
"price": "Preço",
"density": "Densidade",
"diameter": "Diâmetro",
"weight": "Peso",
"article_number": "Número do Artigo",
"registered": "Registrado",
"settings_extruder_temp": "Temperatura da Extrusora",
"settings_bed_temp": "Temperatura da Mesa",
"single_color": "Única",
"multi_color": "Múltiplas",
"coaxial": "Coextrusada",
"external_id": "ID Externo",
"spools": "Mostrar Carretéis",
"id": "ID",
"vendor_name": "Fabricante",
"vendor": "Fabricante",
"spool_weight": "Peso do Carretel",
"color_hex": "Cor",
"comment": "Comentário",
"longitudinal": "Longitudinal"
},
"fields_help": {
"name": "Nome do filamento, para distinguir este tipo de filamento entre outros do mesmo fabricante. Deve conter a cor, por exemplo.",
"material": "Por exemplo: PLA, ABS, PETG, etc.",
"price": "Preço de um carretel cheio.",
"article_number": "Por exemplo: EAN, UPC, etc.",
"spool_weight": "O peso de um carretel vazio. Usado para determinar o peso medido de um carretel.",
"multi_color_direction": "Os filamentos podem ter múltiplas cores de duas maneiras: por meio de coextrusão, como os filamentos bicolores com as cores consistentes ao longo do carretel, ou por meio de mudanças longitudinais de cor, como os filamentos gradientes que mudam as cores ao longo do carretel.",
"weight": "O peso do filamento de um carretel cheio (peso líquido). Isso não deve incluir o peso do carretel em si, apenas o filamento. É o que normalmente está escrito na embalagem."
},
"titles": {
"create": "Criar Filamento",
"edit": "Editar Filamento",
"list": "Filamentos",
"show": "Mostrar Filamento",
"show_title": "[Filamento #{{id}}] {{name}}",
"clone": "Clonar Filamento"
},
"form": {
"import_external": "Importar do Exterior",
"import_external_description": "Selecione um filamento na lista para preencher automaticamente seus detalhes no formulário de criação de filamento. Isso substituirá quaisquer dados que você inseriu no formulário.<br><br>Um objeto Fabricante será criado automaticamente quando você clicar em OK, se necessário.",
"filament_updated": "Este filamento foi atualizado por alguém/outra coisa desde que você abriu esta página. Salvar irá sobrescrever essas alterações!"
},
"buttons": {
"add_spool": "Adicionar Carretel"
},
"filament": "Filamentos"
},
"vendor": {
"vendor": "Fabricantes",
"fields": {
"id": "ID",
"name": "Nome",
"empty_spool_weight": "Peso do Carretel Vazio",
"external_id": "ID Externo",
"registered": "Registrado",
"comment": "Comentário"
},
"fields_help": {
"empty_spool_weight": "O peso de um carretel vazio deste fabricante."
},
"titles": {
"create": "Criar Fabricante",
"clone": "Clonar Fabricante",
"edit": "Editar Fabricante",
"list": "Fabricantes",
"show_title": "[Fabricante #{{id}}] {{name}}",
"show": "Mostrar Fabricante"
},
"form": {
"vendor_updated": "Este fabricante foi atualizado por alguém/alguma coisa desde que você abriu esta página. Salvar sobrescreverá essas alterações!"
}
},
"help": {
"resources": {
"spool": "Carretéis físicos individuais de um filamento específico.",
"vendor": "As empresas que fabricam o filamento.",
"filament": "Marcas de filamento. Elas têm propriedades como nome, material, cor, diâmetro e mais."
},
"help": "Ajuda",
"description": "<title>Ajuda</title><p>Aqui estão algumas dicas para você começar.</p><p>O Spoolman contém três tipos diferentes de dados:</p><itemsHelp/><p>Para adicionar um novo carretel para o banco de dados, comece criando um objeto <filamentCreateLink>Filamento</filamentCreateLink>. Feito isso, prossiga para criar um objeto <spoolCreateLink>Carretel</spoolCreateLink> para esse carretel específico. Se você adquirir carretéis adicionais do mesmo filamento posteriormente, simplesmente crie carretéis adicionais e reutilize o mesmo objeto Filamento.</p><p>Opcionalmente, você também pode gerar um objeto <vendorCreateLink>Fabricante</vendorCreateLink> para a empresa que fabrica o filamento, se você desejar rastrear essas informações.</p><p>Você tem a opção de vincular outros serviços de impressora 3D ao Spoolman, como o Moonraker, o qual pode automaticamente monitorar o uso do filamento e atualizar os carretéis para você. Consulte o <readmeLink>LEIA-ME do Spoolman</readmeLink> para obter orientação sobre como configurar isso.</p>"
},
"settings": {
"header": "Configurações",
"general": {
"currency": {
"label": "Moeda"
},
"base_url": {
"label": "URL Base",
"tooltip": "A URL base a ser usada ao gerar recursos como códigos QR."
},
"tab": "Geral",
"round_prices": {
"label": "Arredondar preços",
"tooltip": "Arredonda os preços para o número inteiro mais próximo."
}
},
"extra_fields": {
"tab": "Campos Extras",
"params": {
"name": "Nome",
"default_value": "Valor Padrão",
"choices": "Opções",
"multi_choice": "Múltipla Escolha",
"order": "Ordem",
"key": "Chave",
"unit": "Unidade",
"field_type": "Tipo"
},
"field_type": {
"text": "Texto",
"integer": "Inteiro",
"integer_range": "Intervalo de Inteiros",
"float": "Decimal",
"float_range": "Intervalo Decimal",
"datetime": "Data e Hora",
"boolean": "Booleano",
"choice": "Opção"
},
"boolean_false": "Não",
"choices_missing_error": "Você não pode remover as opções. As seguintes opções estão faltando: {{choices}}",
"non_unique_key_error": "A chave deve ser única.",
"delete_confirm_description": "Isso excluirá o campo e todos os dados associados em todas as entidades.",
"key_not_changed": "Por favor, escolha outra chave.",
"delete_confirm": "Apagar o campo {{name}}?",
"boolean_true": "Sim",
"description": "<p>Aqui você pode adicionar campos personalizados extras às suas entidades.</p><p>Depois que um campo é adicionado, você não pode alterar sua chave ou tipo, e para campos do tipo \"escolha\" você não pode remover as opções ou alterar o estado de \"múltipla de escolha\". Se você remover um campo, os dados associados à ela em todas as entidades serão excluídos.</p><p>A chave é como outros programas leem/gravam os dados, portanto, se o seu campo personalizado for para uma integração com um programa de terceiros, certifique-se de configurá-lo corretamente. O valor padrão é aplicado somente a novos itens.</p><p>Campos extras não podem ser ordenados ou filtrados nas visualizações de tabela.</p>"
},
"settings": "Configurações"
},
"documentTitle": {
"default": "Spoolman",
"home": {
"list": "Início | Spoolman"
},
"help": {
"list": "Ajuda | Spoolman"
},
"filament": {
"edit": "Editar Filamento #{{id}} | Spoolman",
"create": "Criar Filamento | Spoolman",
"clone": "Clonar Filamento #{{id}} | Spoolman",
"show": "Mostrar Filamento #{{id}} | Spoolman",
"list": "Filamentos | Spoolman"
},
"spool": {
"list": "Carretéis | Spoolman",
"show": "Mostrar Carretel #{{id}} | Spoolman",
"edit": "Editar Carretel #{{id}} | Spoolman",
"clone": "Clonar Carretel #{{id}} | Spoolman",
"create": "Criar Carretel | Spoolman"
},
"vendor": {
"list": "Fabricantes | Spoolman",
"create": "Criar Fabricante | Spoolman",
"clone": "Clonar Fabricante #{{id}} | Spoolman",
"show": "Mostrar Fabricante #{{id}} | Spoolman",
"edit": "Editar Fabricante #{{id}} | Spoolman"
},
"suffix": " | Spoolman",
"locations": {
"list": "Locais | Spoolman"
}
},
"locations": {
"no_locations_help": "Esta página permite que você organize seus carretéis em locais, adicione alguns carretéis para começar!",
"locations": "Locais",
"new_location": "Novo Local",
"no_location": "Sem Local"
},
"version": "Versão",
"no": "Não",
"actions": {
"create": "Criar",
"edit": "Editar",
"clone": "Clonar",
"show": "Mostrar",
"list": "Listar"
},
"warnWhenUnsavedChanges": "Você tem certeza que quer sair? Você tem alterações não salvas.",
"notifications": {
"success": "Sucesso",
"error": "Erro (código de status: {{statusCode}})",
"undoable": "Você tem {{seconds}} segundos para desfazer.",
"createSuccess": "{{resource}} criado com sucesso",
"createError": "Ocorreu um erro ao criar {{resource}} (código de status: {{statusCode}})",
"deleteSuccess": "{{resource}} excluído com sucesso",
"deleteError": "Erro ao excluir {{resource}} (código de status: {{statusCode}})",
"editError": "Erro ao editar {{resource}} (código de status: {{statusCode}})",
"importProgress": "Importando: {{processed}}/{{total}}",
"saveSuccessful": "Salvo com sucesso!",
"validationError": "Erro de Validação: {{error}}",
"editSuccess": "Editado com sucesso {{resource}}"
},
"kofi": "Doe pelo Ko-fi",
"loading": "Carregando",
"unknown": "Desconhecido",
"yes": "Sim",
"dashboard": {
"title": "Painel"
},
"scanner": {
"title": "Leitor de Código QR",
"description": "Escaneie um código QR do Spoolman para ver detalhes sobre o carretel.",
"error": {
"notAllowed": "Você não deu permissão de acesso à câmera.",
"insecureContext": "A página não é servida por HTTPS.",
"streamApiNotSupported": "O navegador não oferece suporte à API MediaStream.",
"notReadable": "A câmera não está legível.",
"unknown": "Ocorreu um erro desconhecido. ({{error}})",
"notFound": "Nenhuma câmera foi encontrada."
}
},
"home": {
"home": "Início"
},
"table": {
"actions": "Ações"
}
}

View File

@@ -23,19 +23,19 @@
"archive": "Arquivar",
"unArchive": "Desarquivar",
"hideArchived": "Ocultar Arquivados",
"showArchived": "Mostrar Arquivado",
"notAccessTitle": "Você não tem permissão para acessar",
"showArchived": "Mostrar Arquivados",
"notAccessTitle": "Não tem permissão para aceder",
"hideColumns": "Ocultar Colunas",
"clearFilters": "Limpar Filtros",
"create": "Criar",
"saveAndAdd": "Guardar e Adicionar",
"continue": "Continuar"
},
"warnWhenUnsavedChanges": "Você tem certeza de que quer sair? Você tem alterações não salvas.",
"warnWhenUnsavedChanges": "Tem certeza de que quer sair? Tem alterações não guardadas.",
"notifications": {
"success": "Sucesso",
"error": "Erro (código de status: {{statusCode}})",
"undoable": "Você tem {{seconds}} segundos para desfazer.",
"undoable": "Tem {{seconds}} segundos para desfazer.",
"createSuccess": "{{resource}} criado com sucesso",
"createError": "Ocorreu um erro ao criar {{resource}} (código de status: {{statusCode}})",
"deleteSuccess": "{{resource}} excluído com sucesso",
@@ -43,7 +43,7 @@
"editSuccess": "{{resource}} editado com sucesso",
"editError": "Erro ao editar {{resource}} (código de status: {{statusCode}})",
"importProgress": "Importando: {{processed}}/{{total}}",
"saveSuccessful": "Salvo com sucesso!",
"saveSuccessful": "Guardado com sucesso!",
"validationError": "Erro de validação: {{error}}"
},
"kofi": "Doe no Ko-fi",
@@ -106,8 +106,8 @@
"selectedTotal_many": "{{count}} bobines selecionados",
"selectedTotal_other": "{{count}} bobines selecionados",
"noSpoolsSelected": "Não selecionou nenhuma bobine.",
"title": "Selecionar Carretéis",
"description": "Selecione os carretéis para imprimir as etiquetas.",
"title": "Selecionar Bobines",
"description": "Selecione as bobines para imprimir as etiquetas.",
"showArchived": "Mostrar Arquivado",
"selectAll": "Selecionar/Desmarcar Tudo"
},
@@ -174,7 +174,7 @@
},
"fields_help": {
"weight_to_use": "Selecione qual o valor de peso a inserir. O peso medido só estará disponível se o peso da bobine estiver definido para o filamento selecionado.",
"lot_nr": "Número de lote do fabricante. Pode ser usado para garantir que uma impressão tenha cores consistentes se vários carretéis forem usados.",
"lot_nr": "Número de lote do fabricante. Pode ser usado para garantir que uma impressão tenha cores consistentes se várias bobines forem usadas.",
"used_weight": "Quanto filamento foi usado da bobine. Uma nova bobine deve ter usado 0g.",
"remaining_weight": "Quanto filamento resta na bobine. Para uma bobine nova, deve corresponder ao peso da bobine.",
"measured_weight": "Quanto pesa o filamento e a bobine.",
@@ -188,7 +188,7 @@
"create": "Criar Bobine",
"clone": "Clonar Bobine",
"edit": "Editar Bobine",
"list": "Carretéis",
"list": "Bobines",
"show": "Mostrar Bobine",
"show_title": "[Bobine #{{id}}] {{name}}",
"archive": "Arquivar Bobine",
@@ -208,7 +208,7 @@
"messages": {
"archive": "Tem certeza de que deseja arquivar esta bobine?"
},
"spool": "Carretéis",
"spool": "Bobines",
"formats": {
"last_used": "Última utilização {{date}}"
}
@@ -232,7 +232,7 @@
"settings_extruder_temp": "Temperatura do Bico",
"settings_bed_temp": "Temperatura da Mesa",
"color_hex": "Cor",
"spools": "Mostrar Carretéis",
"spools": "Mostrar Bobines",
"external_id": "ID Externo",
"single_color": "Único",
"multi_color": "Múltiplo",
@@ -246,7 +246,7 @@
"material": "Por exemplo PLA, ABS, PETG, etc.",
"weight": "O peso do filamento de uma bobine cheia (peso líquido). Isso não deve incluir o peso da bobine em si, apenas o filamento. É o que normalmente está escrito na embalagem.",
"spool_weight": "O peso de uma bobine vazia. Usado para determinar o peso medido de uma bobine.",
"multi_color_direction": "Os filamentos podem ter múltiplas cores de duas formas: por coextrusão, como os filamentos de duas cores com estas consistentes ao longo do filamento, or através de mudança de cor longitudinal, como os filamentos que contêm gradientes de cores que vão mudando ao longo da bobine."
"multi_color_direction": "Os filamentos podem ter múltiplas cores de duas formas: por coextrusão, como os filamentos de duas cores com estas consistentes ao longo do filamento, ou através de mudança de cor longitudinal, como os filamentos que contêm gradientes de cores que vão mudando ao longo da bobine."
},
"titles": {
"create": "Criar Filamento",
@@ -297,7 +297,7 @@
"help": "Ajuda",
"resources": {
"filament": "Marcas de filamento. Eles têm propriedades como nome, material, cor, diâmetro e muito mais.",
"spool": "Carretéis físicos individuais de um filamento específico.",
"spool": "Bobines físicas individuais de um filamento específico.",
"vendor": "As empresas que fabricam o filamento."
},
"description": "<title>Ajuda</title><p>Aqui estão algumas dicas para começar.</p><p>O Spoolman contém três tipos diferentes de dados:</p><itemsHelp/><p>Para adicionar uma nova bobine para a base de dados, comece por criar um <filamentCreateLink>Filamento</filamentCreateLink>. Feito isso, prossiga para criar uma <spoolCreateLink>Bobine</spoolCreateLink> para esse filamento específico. Se posteriormente adquirir bobines adicionais do mesmo filamento, simplesmente gere Bobines adicionais e reutilize o mesmo objeto Filamento.</p><p>Opcionalmente, também pode criar um <vendorCreateLink>Fabricante</vendorCreateLink> para a empresa que fabrica o filamento, se desejar rastrear essas informações.</p><p>Tem a opção de vincular outros serviços de impressora 3D ao Spoolman, como o Moonraker, que pode monitorizar automaticamente o uso do filamento e atualizar as bobines. Consulte o <readmeLink>README do Spoolman</readmeLink> para obter orientação sobre como configurar.</p>"
@@ -318,7 +318,7 @@
"clone": "#{{id}} Clonar filamento | Spoolman"
},
"spool": {
"list": "Carretéis | Spoolman",
"list": "Bobines | Spoolman",
"show": "#{{id}} Mostrar Bobine | Spoolman",
"edit": "#{{id}} Editar Bobine | Spoolman",
"create": "Criar Bobine | Spoolman",
@@ -341,12 +341,12 @@
"title": "Leitor de Código QR",
"description": "Digitalize um código QR do Spoolman para ver detalhes sobre a bobine.",
"error": {
"notAllowed": "Você não deu permissão de acesso à câmera.",
"notAllowed": "Não deu permissão de acesso à câmara.",
"insecureContext": "A página não é protegida por HTTPS.",
"streamApiNotSupported": "O navegador não oferece suporte à API MediaStream.",
"unknown": "Ocorreu um erro desconhecido. ({{error}})",
"notReadable": "A câmera não está acessível.",
"notFound": "Nenhuma câmera foi encontrada."
"notReadable": "A câmara não está acessível.",
"notFound": "Nenhuma câmara foi encontrada."
}
},
"table": {
@@ -377,8 +377,8 @@
"key_not_changed": "Por favor, mude a chave por outra.",
"delete_confirm": "Apagar o campo {{name}}?",
"delete_confirm_description": "Isto irá apagar o campo e todos os dados associados em todas as entidades.",
"description": "<p>Aqui você pode adicionar campos personalizados extras às suas entidades.</p><p>Uma vez que um campo é adicionado, você não pode alterar sua chave ou tipo, e para campos do tipo \"opção\" você não pode remover as opções ou alterar o estado de múltipla opção. Se você remover um campo, os dados associados para todas as entidades serão excluídos.</p><p>A chave é como outros programas leem/gravam os dados, portanto, se seu campo personalizado se destina a se integrar com um programa de terceiros, certifique-se de defini-lo corretamente. O valor padrão só é aplicado a novos itens.</p><p>Campos extras não podem ser ordenados ou filtrados nas visualizações de tabela.</p>",
"choices_missing_error": "Você não pode remover as opções. As seguintes opções serão perdidas: {{choices}}",
"description": "<p>Aqui pode adicionar campos personalizados extras às suas entidades.</p><p>Uma vez que um campo é adicionado, não se pode alterar sua chave ou tipo, e para campos do tipo \"opção\" não se pode remover as opções ou alterar o estado da múltipla opção. Se remover um campo, os dados associados para todas as entidades serão excluídos.</p><p>A chave é como outros programas leem/gravam os dados, portanto, se o seu campo personalizado se destina a integrar com um programa de terceiros, certifique-se de defini-lo corretamente. O valor padrão só é aplicado a novos itens.</p><p>Campos extras não podem ser ordenados ou filtrados nas visualizações de tabela.</p>",
"choices_missing_error": "Não pode remover as opções. As seguintes opções serão perdidas: {{choices}}",
"tab": "Campos Extras",
"boolean_true": "Sim",
"boolean_false": "Não",
@@ -394,6 +394,10 @@
"base_url": {
"label": "Endereço Base",
"tooltip": "O endereço base a utilizar em funcionalidades tais como os códigos QR."
},
"round_prices": {
"tooltip": "Arredonda os preços para o número inteiro mais próximo.",
"label": "Arredondar preços"
}
}
},

View File

@@ -121,7 +121,7 @@
"template": "Шаблон этикетки",
"templateHelp": "Используйте {} для вставки значений объекта катушки в виде текста. Например, {id} будет заменен на идентификатор катушки, или {filament.material} будет заменен на материал катушки. если значение отсутствует, оно будет заменено на \"?\". Для удаления этого можно использовать второй набор {}. Кроме того, любой текст между наборами {} будет удален, если значение отсутствует. Например, {Номер партии: {lot_nr}} будет отображаться только в том случае, если на катушке указан номер партии. Для выделения текста жирным шрифтом выделите его двойной звездочкой **. Нажмите кнопку, чтобы просмотреть список всех доступных тегов.",
"useHTTPUrl": {
"tooltip": "Будет использоваться фирменная ссылка, которая будет работать только при сканировании с помощью функции сканирования Spoolman (по умолчанию). В качестве URL-адреса используется либо базовый URL-адрес, указанный в настройках, либо URL-адрес текущей страницы, если он не задан.",
"tooltip": "Будет использоваться проприетарная ссылка, которая будет работать только при сканировании через функцию сканирования Spoolman (по умолчанию). URL использует либо базовый URL, указанный в настройках, либо URL текущей страницы, если он не задан.",
"label": "Ссылка на QR-код",
"options": {
"default": "По умолчанию",
@@ -220,6 +220,9 @@
"adjust_filament_help": "Здесь вы можете напрямую добавлять или вычитать филамент из катушки. При положительном значении филамент будет израсходован, при отрицательном - добавлен.",
"measurement_type_label": "Тип измерения",
"adjust_filament_value": "Потребляемое количество"
},
"formats": {
"last_used": "Последнее использование {{date}}"
}
},
"filament": {
@@ -331,6 +334,9 @@
},
"home": {
"list": "Главная | Spoolman"
},
"locations": {
"list": "Местоположения | Spoolman"
}
},
"help": {
@@ -387,8 +393,18 @@
"base_url": {
"label": "Базовый URL-адрес",
"tooltip": "Базовый URL-адрес, используемый при создании таких функций, как QR-коды."
},
"round_prices": {
"tooltip": "Округлять цены до ближайшего целого числа.",
"label": "Округлять цены"
}
},
"settings": "Настройки"
},
"locations": {
"no_locations_help": "Эта страница позволяет организовать катушки по местоположениям. Добавьте несколько катушек, чтобы начать!",
"locations": "Местоположения",
"new_location": "Новое местоположение",
"no_location": "Нет местоположения"
}
}

View File

@@ -0,0 +1,397 @@
{
"buttons": {
"hideArchived": "காப்பகப்படுத்தப்பட்டதை மறைக்கவும்",
"create": "உருவாக்கு",
"save": "சேமி",
"saveAndAdd": "சேமித்து சேர்க்கவும்",
"logout": "விடுபதிகை",
"delete": "நீக்கு",
"edit": "தொகு",
"cancel": "ரத்துசெய்",
"confirm": "நீங்கள் உறுதியாக இருக்கிறீர்களா?",
"continue": "தொடரவும்",
"filter": "வடிப்பி",
"clear": "தெளிவான",
"refresh": "புதுப்பிப்பு",
"show": "காட்டு",
"undo": "செயல்தவிர்",
"import": "இறக்குமதி",
"clone": "நகலி",
"archive": "காப்பகம்",
"unArchive": "அன்கான்",
"showArchived": "காப்பகப்படுத்தப்பட்டதைக் காட்டு",
"notAccessTitle": "அணுக உங்களுக்கு இசைவு இல்லை",
"hideColumns": "நெடுவரிசைகளை மறைக்கவும்",
"clearFilters": "தெளிவான வடிப்பான்கள்"
},
"printing": {
"generic": {
"itemCopies": "உருப்படி நகல்கள்",
"title": "அச்சிடுதல்",
"description": "விரும்பிய அச்சு தளவமைப்பைப் பெற கீழேயுள்ள அமைப்புகளில் டியூன் செய்யுங்கள். அச்சுப்பொறிகளும் உங்கள் ஓஎச்வும் அதன் சொந்த ஓரங்கள் மற்றும் அளவிடுதலைப் பயன்படுத்தலாம் என்பதை நினைவில் கொள்ளுங்கள், எனவே இவை அனைத்தும் சரியானதாக இருப்பதற்கு முன்பு நீங்கள் சில சோதனை மற்றும் பிழை செய்ய வேண்டியிருக்கும். உண்மையான லேபிள்களில் அச்சிடுவதற்கு முன் வழக்கமான காகிதத்தில் அதை சோதிக்கவும்.",
"helpMargin": "உங்கள் சிட்டை காகிதம் மற்றும் அச்சுப்பொறியுடன் பொருந்தக்கூடிய வகையில் விளிம்புகள் கட்டமைக்கப்பட வேண்டும், இவற்றை மாற்றுவது முழு கட்டத்தின் அளவையும் பாதிக்கும்.",
"helpPrinterMargin": "உங்கள் அச்சுப்பொறி எவ்வளவு நெருக்கமாக அச்சிட முடியும் என்பதற்கு பாதுகாப்பான-மண்டலத்தை அமைக்க வேண்டும், இவற்றை மாற்றுவது முழு கட்டத்தையும் பாதிக்காது.",
"print": "அச்சிடுக",
"columns": "நெடுவரிசைகள்",
"rows": "வரிசைகள்",
"paperSize": "காகித அளவு",
"customSize": "தனிப்பயன்",
"dimensions": "பரிமாணங்கள்",
"showBorder": "எல்லையைக் காட்டு",
"previewScale": "முன்னோட்டம் அளவு",
"skipItems": "உருப்படிகளைத் தவிர்க்கவும்",
"contentSettings": "உள்ளடக்க அமைப்புகள்",
"layoutSettings": "தளவமைப்பு அமைப்புகள்",
"horizontalSpacing": "கிடைமட்ட இடைவெளி",
"verticalSpacing": "செங்குத்து இடைவெளி",
"marginLeft": "இடது விளிம்பு",
"marginRight": "வலது விளிம்பு",
"marginTop": "மேல் விளிம்பு",
"marginBottom": "கீழ் விளிம்பு",
"printerMarginLeft": "பாதுகாப்பான மண்டலம் இடது",
"printerMarginRight": "பாதுகாப்பான-மண்டல உரிமை",
"printerMarginTop": "பாதுகாப்பான-மண்டல மேல்",
"printerMarginBottom": "பாதுகாப்பான-மண்டல கீழே",
"borders": {
"none": "எதுவுமில்லை",
"border": "எல்லை",
"grid": "வலைவாய்"
},
"settings": "முன்னமைவுகள்",
"defaultSettings": "இயல்புநிலை",
"addSettings": "புதிய முன்னமைவைச் சேர்க்கவும்",
"newSetting": "புதிய",
"duplicateSettings": "நகல்",
"deleteSettings": "தற்போதைய முன்னமைவை நீக்கு",
"deleteSettingsConfirm": "இந்த முன்னமைவை நீக்க விரும்புகிறீர்களா?",
"settingsName": "முன்னமைக்கப்பட்ட பெயர்",
"saveAsImage": "படமாக சேமிக்கவும்",
"saveSetting": "முன்னமைவுகளை சேமிக்கவும்"
},
"qrcode": {
"button": "லேபிள்களை அச்சிடுக",
"title": "சிட்டை அச்சிடுதல்",
"template": "சிட்டை வார்ப்புரு",
"templateHelp": "ச்பூல் பொருளின் மதிப்புகளை உரையாக செருக {} ஐப் பயன்படுத்தவும். எடுத்துக்காட்டாக, {id} ச்பூல் ஐடியுடன் மாற்றப்படும், அல்லது {filament.material} ச்பூலின் பொருள் மூலம் மாற்றப்படும். ஒரு மதிப்பு காணவில்லை என்றால் அது \"?\" இதை அகற்ற {of இன் இரண்டாவது தொகுப்பு பயன்படுத்தப்படலாம். கூடுதலாக, மதிப்பு காணவில்லை எனில் {of இன் தொகுப்புகளுக்கு இடையில் எந்த உரையும் அகற்றப்படும். எடுத்துக்காட்டாக, {lot nr: {lot_nr} எச்பி ச்பூலில் நிறைய எண் இருந்தால் மட்டுமே லேபிளைக் காண்பிக்கும். தைரியமாக மாற்ற இரட்டை ஆச்டெரிக்ச் ** உடன் உரையை இணைக்கவும். கிடைக்கக்கூடிய அனைத்து குறிச்சொற்களின் பட்டியலையும் காண பொத்தானைக் சொடுக்கு செய்க.",
"textSize": "உரை அளவு சிட்டை",
"showContent": "சிட்டை அச்சு",
"useHTTPUrl": {
"label": "QR குறியீடு இணைப்பு",
"tooltip": "ச்பூல்மேனின் ச்கேனிங் அம்சத்திலிருந்து (இயல்புநிலை) ச்கேன் செய்யப்பட்டால் மட்டுமே செயல்படும் தனியுரிம இணைப்பைப் பயன்படுத்தும். அமைப்புகளில் குறிப்பிடப்பட்டுள்ள அடிப்படை முகவரி அல்லது தற்போதைய பக்க முகவரி ஐ அமைக்கவில்லை என்றால் முகவரி பயன்படுத்துகிறது.",
"options": {
"default": "இயல்புநிலை",
"url": "முகவரி"
},
"preview": "முன்னோட்டம்:"
},
"showQRCode": "QR குறியீட்டை அச்சிடுக",
"showQRCodeMode": {
"no": "இல்லை",
"simple": "எளிய",
"withIcon": "ஐகானுடன்"
}
},
"spoolSelect": {
"title": "ச்பூல்களைத் தேர்ந்தெடுக்கவும்",
"description": "லேபிள்களை அச்சிட ச்பூல்களைத் தேர்ந்தெடுக்கவும்.",
"showArchived": "காப்பகப்படுத்தப்பட்டதைக் காட்டு",
"noSpoolsSelected": "நீங்கள் எந்த ச்பூல்களையும் தேர்வு செய்யவில்லை.",
"selectAll": "அனைத்தையும் தேர்ந்தெடு/தேர்வுநீக்கு",
"selectedTotal_one": "{{count}} ச்பூல் தேர்ந்தெடுக்கப்பட்டது",
"selectedTotal_other": "{{count}} spools தேர்ந்தெடுக்கப்பட்டவை"
}
},
"spool": {
"titles": {
"create": "ச்பூலை உருவாக்கவும்",
"clone": "நகலி ச்பூல்",
"edit": "திருத்து ச்பூல்",
"list": "ச்பூல்கள்",
"show": "ச்பூல் காட்டு",
"show_title": "[ச்பூல் #{{id}}] {{name}}",
"archive": "காப்பக ச்பூல்",
"adjust": "ச்பூல் இழை சரிசெய்யவும்"
},
"messages": {
"archive": "இந்த ச்பூலை காப்பகப்படுத்த விரும்புகிறீர்களா?"
},
"spool": "ச்பூல்கள்",
"fields": {
"id": "ஐடி",
"filament_name": "தாள்",
"filament": "தாள்",
"remaining_weight": "மீதமுள்ள எடை",
"filament_internal": "உள்",
"filament_external": "வெளிப்புறம்",
"price": "விலை",
"material": "பொருள்",
"weight_to_use": "எடை",
"used_weight": "பயன்படுத்தப்பட்ட எடை",
"measured_weight": "அளவிடப்பட்ட எடை",
"used_length": "பயன்படுத்தப்பட்ட நீளம்",
"remaining_length": "மீதமுள்ள நீளம்",
"initial_weight": "தொடக்க எடை",
"spool_weight": "வெற்று எடை",
"location": "இடம்",
"lot_nr": "நிறைய இல்லை",
"first_used": "முதலில் பயன்படுத்தப்பட்டது",
"last_used": "கடைசியாக பயன்படுத்தப்பட்டது",
"registered": "பதிவுசெய்யப்பட்டது",
"comment": "கருத்து",
"archived": "காப்பகப்படுத்தப்பட்டது"
},
"fields_help": {
"price": "ஒரு முழு ச்பூலின் விலை. அமைக்கப்படாவிட்டால், அதற்கு பதிலாக இழைகளின் விலை ஏற்றுக்கொள்ளப்படும்.",
"weight_to_use": "என்ன எடை மதிப்பை உள்ளிட வேண்டும் என்பதைத் தேர்ந்தெடுக்கவும். தேர்ந்தெடுக்கப்பட்ட இழைக்கு ச்பூல் எடை அமைக்கப்பட்டால் மட்டுமே அளவிடப்பட்ட எடை கிடைக்கும்.",
"remaining_weight": "ச்பூலில் எவ்வளவு இழை விடப்பட்டுள்ளது. ஒரு புதிய ச்பூலுக்கு இது ச்பூல் எடையுடன் பொருந்த வேண்டும்.",
"used_weight": "ச்பூலில் இருந்து எவ்வளவு இழை பயன்படுத்தப்பட்டுள்ளது. ஒரு புதிய ச்பூல் 0 ஐயா பயன்படுத்தப்பட வேண்டும்.",
"measured_weight": "இழை மற்றும் ச்பூல் எவ்வளவு எடை கொண்டது.",
"initial_weight": "ச்பூலில் (நிகர எடை) இழைகளின் ஆரம்ப எடை. அமைக்காவிட்டால் இழை பொருளிலிருந்து எடையைப் பயன்படுத்தும்.",
"spool_weight": "ச்பூலின் காலியாக இருக்கும்போது அது எடை. இழை அல்லது உற்பத்தியாளரிடமிருந்து மதிப்பைப் பயன்படுத்த காலியாக விடவும்.",
"location": "உங்கள் ச்பூல்களை சேமிக்கும் பல இடங்கள் இருந்தால் ச்பூல் அமைந்துள்ள இடத்தில்.",
"lot_nr": "உற்பத்தியாளரின் நிறைய எண். பல ச்பூல்கள் பயன்படுத்தப்பட்டால் அச்சுக்கு நிலையான வண்ணம் இருப்பதை உறுதிப்படுத்த பயன்படுத்தலாம்.",
"external_filament": "வெளிப்புற தரவுத்தளத்திலிருந்து ஒரு இழை தேர்வு செய்துள்ளீர்கள். இந்த ச்பூலை நீங்கள் உருவாக்கும்போது ஒரு இழை பொருள் (மற்றும் ஒரு உற்பத்தியாளர் பொருள்) தானாக உருவாக்கப்படும். இந்த இழைக்கு நீங்கள் ஏற்கனவே ஒரு இழை பொருளை உருவாக்கியிருந்தால் இது நகல் இழை பொருள்களை உருவாக்க முடியும்."
},
"form": {
"measurement_type": {
"length": "நீளம்",
"weight": "எடை"
},
"measurement_type_label": "அளவீட்டு வகை",
"adjust_filament_help": "இங்கே நீங்கள் நேரடியாக ச்பூலில் இருந்து இழைகளைச் சேர்க்கலாம் அல்லது கழிக்கலாம். நேர்மறையான மதிப்பு இழையை நுகரும், எதிர்மறை மதிப்பு அதைச் சேர்க்கும்.",
"adjust_filament_value": "தொகையை உட்கொள்ளுங்கள்",
"new_location_prompt": "புதிய இடத்தை உள்ளிடவும்",
"spool_updated": "இந்த பக்கத்தைத் திறந்ததிலிருந்து இந்த ச்பூல் யாரோ/வேறு ஏதாவது புதுப்பிக்கப்பட்டுள்ளது. சேமிப்பது அந்த மாற்றங்களை மேலெழுதும்!"
},
"formats": {
"last_used": "கடைசியாக பயன்படுத்தப்பட்ட {{date}}"
}
},
"filament": {
"fields": {
"coaxial": "இணை",
"longitudinal": "நீளமான",
"external_id": "வெளிப்புற ஐடி",
"id": "ஐடி",
"vendor_name": "உற்பத்தியாளர்",
"vendor": "உற்பத்தியாளர்",
"name": "பெயர்",
"material": "பொருள்",
"price": "விலை",
"density": "அடர்த்தி",
"diameter": "விட்டம்",
"weight": "எடை",
"spool_weight": "ச்பூல் எடை",
"article_number": "கட்டுரை எண்",
"registered": "பதிவுசெய்யப்பட்டது",
"comment": "கருத்து",
"settings_extruder_temp": "எக்ச்ட்ரூடர் தற்காலிக",
"settings_bed_temp": "படுக்கை தற்காலிக",
"color_hex": "நிறம்",
"single_color": "ஒற்றை",
"multi_color": "பல",
"spools": "ச்பூல்களைக் காட்டு"
},
"fields_help": {
"article_number": "எ.கா. EAN, UPC, முதலியன.",
"material": "எ.கா. பி.எல்.ஏ, ஏபிஎச், பெட்சி, முதலியன.",
"name": "இழை பெயர், இந்த இழை வகையை ஒரே உற்பத்தியாளரிடமிருந்து வேறுபடுத்துவதற்கு. உதாரணமாக வண்ணம் இருக்க வேண்டும்.",
"price": "ஒரு முழு ச்பூலின் விலை.",
"weight": "ஒரு முழு ச்பூலின் இழை எடை (நிகர எடை). இதில் ச்பூலின் எடையை சேர்க்கக்கூடாது, இழை மட்டுமே. இது பொதுவாக பேக்கேசிங்கில் எழுதப்படுகிறது.",
"spool_weight": "வெற்று ச்பூலின் எடை. ஒரு ச்பூலின் அளவிடப்பட்ட எடையை தீர்மானிக்கப் பயன்படுகிறது.",
"multi_color_direction": "இழைகள் இரண்டு வழிகளில் பல வண்ணங்களைக் கொண்டிருக்கலாம்: சகாக்களின் மூலம், நிலையான பல வண்ணங்களுடன் இரட்டை-வண்ண இழைகள் போன்றவை, அல்லது ச்பூலில் வண்ணங்களை மாற்றும் சாய்வு இழைகள் போன்ற நீளமான வண்ண மாற்றங்கள் மூலம்."
},
"titles": {
"list": "இழைகள்",
"show": "இழை காட்டு",
"create": "இழை உருவாக்கு",
"clone": "நகலி இழை",
"edit": "இழை திருத்து",
"show_title": "[இழை #{{id}}] {{name}}"
},
"filament": "இழைகள்",
"form": {
"filament_updated": "இந்த பக்கத்தைத் திறந்ததிலிருந்து இந்த இழை யாரோ/வேறு ஏதாவது புதுப்பிக்கப்பட்டுள்ளது. சேமிப்பது அந்த மாற்றங்களை மேலெழுதும்!",
"import_external": "வெளிப்புறத்திலிருந்து இறக்குமதி",
"import_external_description": "அதன் விவரங்களை இழை உருவாக்கும் வடிவத்தில் தானாக விரிவுபடுத்தப் பட்டியலில் ஒரு இழை தேர்ந்தெடுக்கவும். இது நீங்கள் படிவத்தில் உள்ளிட்ட எந்தத் தரவையும் மேலெழுதும். <br> <br> தேவைப்பட்டால், சரி என்பதைக் சொடுக்கு செய்யும்போது ஒரு உற்பத்தியாளர் பொருள் தானாக உருவாக்கப்படும்."
},
"buttons": {
"add_spool": "ச்பூல் சேர்க்கவும்"
}
},
"vendor": {
"fields": {
"name": "பெயர்",
"empty_spool_weight": "வெற்று ச்பூல் எடை",
"id": "ஐடி",
"external_id": "வெளிப்புற ஐடி",
"registered": "பதிவுசெய்யப்பட்டது",
"comment": "கருத்து"
},
"vendor": "உற்பத்தியாளர்கள்",
"fields_help": {
"empty_spool_weight": "இந்த உற்பத்தியாளரிடமிருந்து வெற்று ச்பூலின் எடை."
},
"titles": {
"create": "உற்பத்தியாளரை உருவாக்கவும்",
"clone": "நகலி உற்பத்தியாளர்",
"edit": "உற்பத்தியாளரைத் திருத்து",
"list": "உற்பத்தியாளர்கள்",
"show": "உற்பத்தியாளரைக் காட்டு",
"show_title": "[உற்பத்தியாளர் #{{id}}] {{name}}"
},
"form": {
"vendor_updated": "இந்த பக்கத்தைத் திறந்ததிலிருந்து இந்த உற்பத்தியாளர் யாரோ/வேறு ஏதாவது புதுப்பிக்கப்பட்டார். சேமிப்பது அந்த மாற்றங்களை மேலெழுதும்!"
}
},
"help": {
"resources": {
"vendor": "இழை செய்யும் நிறுவனங்கள்.",
"filament": "இழைகளின் பிராண்டுகள். அவற்றில் பெயர், பொருள், நிறம், விட்டம் மற்றும் பல போன்ற பண்புகள் உள்ளன.",
"spool": "ஒரு குறிப்பிட்ட இழைகளின் தனிப்பட்ட உடல் ச்பூல்கள்."
},
"help": "உதவி",
"description": "<title> உதவி </title> <p> நீங்கள் தொடங்குவதற்கான சில குறிப்புகள் இங்கே. </p> <p> ச்பூல்மேன் 3 வெவ்வேறு வகையான தரவுகளை வைத்திருக்கிறார்: </p> <itemshelp/> <p> தரவுத்தளத்தில் ஒரு புதிய ச்பூலைச் சேர்க்க, <filemamentcreatelink> Filemation </filemationcreatelink> பொருளை உருவாக்குவதன் மூலம் தொடங்கவும். அது முடிந்ததும், அந்த குறிப்பிட்ட ச்பூலுக்கான </spoolcreatelink> ச்பூல் </spoolcreatelink> பொருளை உருவாக்க தொடரவும். அதே இழைகளின் கூடுதல் ச்பூல்களைப் பின்னர் நீங்கள் பெற்றால், கூடுதல் ச்பூல் பொருள்களை உருவாக்கி அதே இழை பொருளை மீண்டும் பயன்படுத்தினால். இழை பயன்பாட்டை தானாக கண்காணித்து, உங்களுக்கான ச்பூல் பொருள்களைப் புதுப்பிக்கவும். அதை எவ்வாறு அமைப்பது என்பதற்கான வழிகாட்டுதலுக்கு </p> குறித்த வழிகாட்டுதலுக்கு <ரீட்மெலிங்க்> ச்பூல்மேன் ரீட்மே </readmelink> ஐப் பார்க்கவும். </P>"
},
"documentTitle": {
"vendor": {
"clone": "#{{id}} நகலி உற்பத்தியாளர் | ச்பூல்மேன்",
"list": "உற்பத்தியாளர்கள் | ச்பூல்மேன்",
"show": "#{{id}} உற்பத்தியாளரைக் காட்டு | ச்பூல்மேன்",
"edit": "#{{id}} உற்பத்தியாளரைத் திருத்து | ச்பூல்மேன்",
"create": "உற்பத்தியாளரை உருவாக்கு | ச்பூல்மேன்"
},
"default": "ச்பூல்மேன்",
"suffix": " | ச்பூல்மேன்",
"home": {
"list": "முகப்பு | ச்பூல்மேன்"
},
"help": {
"list": "உதவி | ச்பூல்மேன்"
},
"locations": {
"list": "இடங்கள் | ச்பூல்மேன்"
},
"filament": {
"list": "இழைகள் | ச்பூல்மேன்",
"show": "#{{id}} ஃபிலிமென்ட் காட்டு | ச்பூல்மேன்",
"edit": "#{{id}} Filuctient தாள் | ச்பூல்மேன்",
"create": "இழை உருவாக்கு | ச்பூல்மேன்",
"clone": "#{{id}} நகலி இழை | ச்பூல்மேன்"
},
"spool": {
"list": "ச்பூல்கள் | ச்பூல்மேன்",
"show": "#{{id}} காட்டு ச்பூல் | ச்பூல்மேன்",
"edit": "#{{id}} திருத்து ச்பூல் | ச்பூல்மேன்",
"create": "ச்பூலை உருவாக்கு | ச்பூல்மேன்",
"clone": "#{{id}} நகலி ச்பூல் | ச்பூல்மேன்"
}
},
"locations": {
"locations": "இருப்பிடங்கள்",
"new_location": "புதிய இடம்",
"no_location": "இடம் இல்லை",
"no_locations_help": "இந்த பக்கம் உங்கள் ச்பூல்களை இருப்பிடங்களில் ஒழுங்கமைக்க அனுமதிக்கிறது, தொடங்குவதற்கு சில ச்பூல்களைச் சேர்க்கவும்!"
},
"actions": {
"list": "பட்டியல்",
"create": "உருவாக்கு",
"edit": "தொகு",
"show": "காட்டு",
"clone": "நகலி"
},
"warnWhenUnsavedChanges": "நீங்கள் நிச்சயமாக வெளியேற விரும்புகிறீர்களா? நீங்கள் சேமிக்கப்படாத மாற்றங்கள் உள்ளன.",
"notifications": {
"success": "செய்",
"error": "பிழை (நிலை குறியீடு: {{statusCode}})",
"undoable": "நீங்கள் செயல்தவிர்க்க {{seconds}} வினாடிகள் உள்ளன.",
"createSuccess": "வெற்றிகரமாக உருவாக்கப்பட்டது {{resource}}",
"createError": "{{resource}} (நிலைக் குறியீடு: {{statusCode}}) உருவாக்கும் பிழை இருந்தது",
"deleteSuccess": "வெற்றிகரமாக நீக்கப்பட்டது {{resource}}",
"deleteError": "{{resource}} நீக்கும்போது பிழை (நிலைக் குறியீடு: {{statusCode}})",
"editSuccess": "வெற்றிகரமாக திருத்தப்பட்டது {{resource}}",
"editError": "{{resource}} திருத்தும்போது பிழை (நிலை குறியீடு: {{statusCode}})",
"importProgress": "இறக்குமதி: {{processed}}/{{total}}}",
"saveSuccessful": "வெற்றிகரமாக சேமிக்கவும்!",
"validationError": "சரிபார்ப்பு பிழை: {{error}}"
},
"kofi": "உதவிக்குறிப்பு மற்றும் ஆன்-ஃபை",
"loading": "ஏற்றுகிறது",
"version": "பதிப்பு",
"unknown": "தெரியவில்லை",
"yes": "ஆம்",
"no": "இல்லை",
"tags": {
"clone": "நகலி"
},
"dashboard": {
"title": "முகப்புப்பெட்டி"
},
"scanner": {
"title": "QR குறியீடு ச்கேனர்",
"description": "ச்பூல் பற்றிய விவரங்களைக் காண ஒரு ச்பூல்மேன் கியூஆர் குறியீட்டை ச்கேன் செய்யுங்கள்.",
"error": {
"notAllowed": "கேமராவை அணுக நீங்கள் அனுமதிக்கவில்லை.",
"insecureContext": "பக்கம் HTTPS க்கு மேல் வழங்கப்படவில்லை.",
"streamApiNotSupported": "உலாவி மீடியாச்ட்ரீம் பநிஇ ஆதரிக்காது.",
"notReadable": "கேமரா படிக்க முடியாது.",
"notFound": "கேமரா எதுவும் கண்டுபிடிக்கப்படவில்லை.",
"unknown": "அறியப்படாத பிழை ஏற்பட்டது. ({{error}})"
}
},
"home": {
"home": "வீடு"
},
"table": {
"actions": "செயல்கள்"
},
"settings": {
"settings": "அமைப்புகள்",
"header": "அமைப்புகள்",
"general": {
"tab": "பொது",
"currency": {
"label": "நாணயம்"
},
"base_url": {
"label": "அடிப்படை முகவரி",
"tooltip": "QR குறியீடுகள் போன்ற அம்சங்களை உருவாக்கும்போது பயன்படுத்த வேண்டிய அடிப்படை முகவரி."
},
"round_prices": {
"label": "சுற்று விலைகள்",
"tooltip": "அருகிலுள்ள முழு எண்ணுக்கு வட்ட விலைகள்."
}
},
"extra_fields": {
"tab": "கூடுதல் புலங்கள்",
"description": "<p> இங்கே நீங்கள் உங்கள் நிறுவனங்களுக்கு கூடுதல் தனிப்பயன் புலங்களைச் சேர்க்கலாம். நீங்கள் ஒரு புலத்தை அகற்றினால், அனைத்து நிறுவனங்களுக்கும் தொடர்புடைய தரவு நீக்கப்படும். </P> <p> மற்ற நிரல்கள் தரவைப் படிக்கிறோம்/எழுதுகின்றன என்பதுதான் முக்கியமானது, எனவே உங்கள் தனிப்பயன் புலம் மூன்றாம் தரப்பு நிரலுடன் ஒருங்கிணைக்க வேண்டுமானால், அதை சரியாக அமைக்க உறுதிசெய்க. இயல்புநிலை மதிப்பு புதிய உருப்படிகளுக்கு மட்டுமே பயன்படுத்தப்படுகிறது. </P> <p> கூடுதல் புலங்களை அட்டவணை காட்சிகளில் வரிசைப்படுத்தவோ வடிகட்டவோ முடியாது. </P>",
"params": {
"key": "விசை",
"name": "பெயர்",
"field_type": "வகை",
"unit": "அலகு",
"order": "ஒழுங்கு",
"default_value": "இயல்புநிலை மதிப்பு",
"choices": "தேர்வுகள்",
"multi_choice": "பல தேர்வு"
},
"field_type": {
"text": "உரை",
"integer": "முழு எண்",
"integer_range": "முழு எண் வரம்பு",
"float": "மிதவை",
"float_range": "மிதவை வரம்பு",
"datetime": "தேதிநேரம்",
"boolean": "பூலியன்",
"choice": "தேர்வு"
},
"boolean_true": "ஆம்",
"boolean_false": "இல்லை",
"choices_missing_error": "நீங்கள் தேர்வுகளை அகற்ற முடியாது. பின்வரும் தேர்வுகள் காணவில்லை: {{choices}}",
"non_unique_key_error": "முக்கியமானது தனித்துவமாக இருக்க வேண்டும்.",
"key_not_changed": "தயவுசெய்து விசையை வேறு ஏதாவது மாற்றவும்.",
"delete_confirm": "புலம் {{name}} ஐ நீக்கு?",
"delete_confirm_description": "இது அனைத்து நிறுவனங்களுக்கும் புலம் மற்றும் தொடர்புடைய அனைத்து தரவையும் நீக்கிவிடும்."
}
}
}

View File

@@ -0,0 +1,392 @@
{
"spool": {
"fields": {
"registered": "ลงทะเบียน",
"remaining_length": "ความยาวคงเหลือ",
"lot_nr": "เลขล็อต",
"archived": "เก็บถาวร",
"remaining_weight": "น้ำหนักคงเหลือ",
"material": "วัสดุ",
"spool_weight": "น้ำหนักม้วนพลาสติกเปล่า",
"id": "ID",
"initial_weight": "น้ำหนักเริ่มต้น",
"measured_weight": "น้ำหนักที่วัดได้",
"used_weight": "น้ำหนักที่ใช้ไป",
"filament_external": "ภายนอก",
"price": "ราคา",
"last_used": "ใช้ครั้งล่าสุด",
"filament_internal": "ภายใน",
"used_length": "ความยาวที่ใช้",
"comment": "หมายเหตุ",
"filament_name": "เส้นพลาสติก",
"first_used": "ใช้ครั้งแรก",
"filament": "เส้นพลาสติก",
"weight_to_use": "น้ำหนัก",
"location": "สถานที่จัดเก็บ"
},
"titles": {
"show_title": "[ม้วนพลาสติก #{{id}}] {{name}}",
"archive": "เก็บถาวรม้วนพลาสติก",
"clone": "ทำซ้ำม้วนพลาสติก",
"create": "สร้างม้วนพลาสติก",
"adjust": "ปรับการใช้ม้วนพลาสติก",
"edit": "แก้ไขม้วนพลาสติก",
"list": "รายการม้วนพลาสติก",
"show": "แสดงม้วนพลาสติก"
},
"form": {
"adjust_filament_value": "ปริมาณที่ใช้",
"measurement_type": {
"length": "ความยาว",
"weight": "น้ำหนัก"
},
"new_location_prompt": "กรอกชื่อสถานที่เก็บใหม่",
"measurement_type_label": "ประเภทการวัด",
"spool_updated": "ม้วนพลาสติกนี้ได้รับการอัปเดตโดยผู้อื่นหลังจากที่คุณเปิดหน้านี้ การบันทึกจะเขียนทับการเปลี่ยนแปลง!",
"adjust_filament_help": "คุณสามารถเพิ่มหรือลดปริมาณเส้นพลาสติกจากม้วนพลาสติกได้ที่นี่ ค่าบวกจะเป็นการใช้เส้นพลาสติก ค่าลบจะเป็นการเติมกลับ"
},
"fields_help": {
"measured_weight": "น้ำหนักรวมของเส้นพลาสติกและม้วนพลาสติก",
"spool_weight": "น้ำหนักของม้วนพลาสติกเมื่อเปล่า หากเว้นว่างไว้จะใช้ค่าจากเส้นพลาสติกหรือจากผู้ผลิต",
"location": "ตำแหน่งที่เก็บม้วนพลาสติก หากคุณมีหลายที่เก็บ นี่คือที่ที่คุณจัดเก็บม้วนพลาสติก",
"initial_weight": "น้ำหนักเส้นพลาสติกในม้วนพลาสติก (สุทธิ) หากไม่ได้ตั้งค่า จะใช้น้ำหนักจากอ็อบเจ็กต์เส้นพลาสติก",
"used_weight": "ปริมาณเส้นพลาสติกที่ถูกใช้ไปจากม้วนพลาสติก ม้วนพลาสติกใหม่ควรเริ่มต้นที่ 0 กรัม",
"price": "ราคาของม้วนพลาสติกเต็ม หากไม่ได้ตั้งค่า จะใช้ราคาของเส้นพลาสติกแทน",
"remaining_weight": "ปริมาณเส้นพลาสติกที่เหลืออยู่ในม้วนพลาสติก สำหรับม้วนพลาสติกใหม่ควรมีค่าเท่ากับน้ำหนักของม้วนพลาสติกเต็ม",
"weight_to_use": "เลือกค่าน้ำหนักที่คุณต้องการระบุ น้ำหนักวัดได้จะใช้ได้เฉพาะเมื่อมีการตั้งค่าน้ำหนักม้วนพลาสติกเปล่าสำหรับเส้นพลาสติกที่เลือก",
"lot_nr": "เลขล็อตของผู้ผลิต ใช้เพื่อให้มั่นใจว่า การพิมพ์จะมีสีสม่ำเสมอ หากต้องใช้พลาสติกหลายม้วน",
"external_filament": "คุณได้เลือกเส้นพลาสติกจากฐานข้อมูลภายนอก อ็อบเจ็กต์เส้นพลาสติก (และอาจมีอ็อบเจ็กต์ผู้ผลิต) จะถูกสร้างโดยอัตโนมัติเมื่อคุณสร้างม้วนพลาสติก อาจทำให้เกิดเส้นพลาสติกซ้ำกัน ถ้าคุณได้สร้างเส้นพลาสติกนั้นไว้อยู่แล้ว"
},
"formats": {
"last_used": "ใช้ครั้งล่าสุด {{date}}"
},
"spool": "ม้วนพลาสติก",
"messages": {
"archive": "คุณแน่ใจหรือไม่ว่าต้องการเก็บถาวรม้วนพลาสติกนี้?"
}
},
"buttons": {
"hideArchived": "ซ่อนที่เก็บถาวร",
"saveAndAdd": "บันทึกและเพิ่ม",
"clearFilters": "ล้างตัวกรอง",
"delete": "ลบ",
"logout": "ออกจากระบบ",
"continue": "ดำเนินการต่อ",
"filter": "กรอง",
"clear": "ล้าง",
"refresh": "รีเฟรช",
"show": "แสดง",
"import": "นำเข้า",
"clone": "ทำซ้ำ",
"save": "บันทึก",
"confirm": "คุณแน่ใจหรือไม่?",
"unArchive": "ยกเลิกเก็บถาวร",
"notAccessTitle": "คุณไม่มีสิทธิ์เข้าถึง",
"create": "สร้าง",
"undo": "ย้อนกลับ",
"cancel": "ยกเลิก",
"hideColumns": "ซ่อนคอลัมน์",
"showArchived": "แสดงที่เก็บถาวร",
"edit": "แก้ไข",
"archive": "เก็บถาวร"
},
"actions": {
"clone": "ทำซ้ำ",
"show": "แสดง",
"edit": "แก้ไข",
"list": "รายการ",
"create": "สร้าง"
},
"printing": {
"generic": {
"columns": "คอลัมน์",
"printerMarginBottom": "Safe-Zone ล่าง",
"printerMarginLeft": "Safe-Zone ซ้าย",
"showBorder": "แสดงเส้นขอบ",
"newSetting": "ใหม่",
"deleteSettings": "ลบพรีเซ็ตปัจจุบัน",
"customSize": "กำหนดเอง",
"skipItems": "ข้ามรายการ",
"print": "พิมพ์",
"dimensions": "ขนาด",
"marginLeft": "ระยะขอบซ้าย",
"verticalSpacing": "ระยะห่างแนวตั้ง",
"marginTop": "ระยะขอบบน",
"layoutSettings": "การตั้งค่าเค้าโครง",
"marginRight": "ระยะขอบขวา",
"helpMargin": "การตั้งค่าขอบกระดาษควรกำหนดให้ตรงกับขนาดกระดาษและเครื่องพิมพ์ของคุณ การเปลี่ยนค่านี้จะมีผลต่อขนาดตารางทั้งหมด",
"helpPrinterMargin": "Safe-Zone ควรกำหนดเป็นค่าระยะห่างจากขอบกระดาษที่เครื่องพิมพ์สามารถพิมพ์ได้ การเปลี่ยนค่านี้จะไม่กระทบต่อขนาดตารางทั้งหมด",
"previewScale": "สเกลตัวอย่าง",
"itemCopies": "สำเนาต่อรายการ",
"borders": {
"grid": "ตาราง",
"none": "ไม่แสดง",
"border": "เส้นขอบ"
},
"settingsName": "ชื่อพรีเซ็ต",
"contentSettings": "การตั้งค่าข้อมูล",
"printerMarginRight": "Safe-Zone ขวา",
"saveAsImage": "บันทึกเป็นรูปภาพ",
"settings": "พรีเซ็ต",
"defaultSettings": "ค่าเริ่มต้น",
"duplicateSettings": "คัดลอกพรีเซ็ต",
"saveSetting": "บันทึกพรีเซ็ต",
"deleteSettingsConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบพรีเซ็ตนี้?",
"addSettings": "เพิ่มพรีเซ็ตใหม่",
"rows": "แถว",
"title": "การพิมพ์",
"horizontalSpacing": "ระยะห่างแนวนอน",
"printerMarginTop": "Safe-Zone บน",
"paperSize": "ขนาดกระดาษ",
"marginBottom": "ระยะขอบล่าง",
"description": "ปรับการตั้งค่าด้านล่างเพื่อให้ได้รูปแบบการพิมพ์ที่ต้องการ โปรดทราบว่าเครื่องพิมพ์และระบบปฏิบัติการของคุณอาจมีการตั้งค่าขอบกระดาษและการปรับขนาดของตัวเอง ดังนั้นคุณอาจต้องลองผิดลองถูกเล็กน้อยก่อนที่จะได้ผลลัพธ์ที่ถูกต้อง แนะนำให้ทดสอบบนกระดาษธรรมดาก่อนที่จะพิมพ์บนกระดาษสติกเกอร์จริง"
},
"qrcode": {
"textSize": "ขนาดข้อความฉลาก",
"useHTTPUrl": {
"options": {
"default": "ค่าเริ่มต้น",
"url": "URL"
},
"label": "ลิงก์ QR code",
"preview": "ตัวอย่าง:",
"tooltip": "จะใช้ลิงก์ภายในที่จะทำงานได้เฉพาะเมื่อสแกนจากฟีเจอร์สแกนของ Spoolman (ค่าเริ่มต้น) URL จะใช้จาก base URL ที่กำหนดไว้ใน settings หรือใช้ URL ของหน้าปัจจุบันหากไม่ได้ตั้งค่า"
},
"showQRCodeMode": {
"withIcon": "พร้อมไอคอน",
"no": "ไม่",
"simple": "ธรรมดา"
},
"template": "เทมเพลตฉลาก",
"button": "พิมพ์ฉลาก",
"title": "การพิมพ์ฉลาก",
"showQRCode": "พิมพ์ QR Code",
"showContent": "พิมพ์ฉลาก",
"templateHelp": "ใช้ {} เพื่อใส่ค่าจากอ็อบเจ็กต์ spool เป็นข้อความ ตัวอย่างเช่น {id} จะถูกแทนที่ด้วย id ของ spool หรือ {filament.material} จะถูกแทนที่ด้วยวัสดุของ spool หากไม่มีค่าจะแสดงเป็น \"?\" สามารถใช้ชุดเครื่องหมาย {} แบบซ้อนเพื่อลบได้ด้วย นอกจากนี้ยังสามารถลบข้อความหากค่านั้นหายไป ตัวอย่าง {Lot Nr: {lot_nr}} จะแสดงฉลากต่อเมื่อ spool มี lot number ใช้เครื่องหมาย ** ครอบข้อความเพื่อทำให้เป็นตัวหนา คลิกปุ่มเพื่อดูรายการแท็กทั้งหมดที่ใช้ได้"
},
"spoolSelect": {
"selectAll": "เลือก/ยกเลิกทั้งหมด",
"title": "เลือกม้วนพลาสติก",
"noSpoolsSelected": "คุณยังไม่ได้เลือกม้วนพลาสติก",
"selectedTotal_other": "เลือก {{count}} ม้วนพลาสติก",
"showArchived": "แสดงที่เก็บถาวร",
"description": "เลือกม้วนพลาสติกสำหรับพิมพ์ฉลาก"
}
},
"filament": {
"fields_help": {
"spool_weight": "น้ำหนักม้วนพลาสติกเปล่า ใช้เพื่อคำนวณน้ำหนักรวมของม้วนพลาสติก",
"material": "ตัวอย่างเช่น PLA, ABS, PETG, TPU, ASA, PC, PA เป็นต้น",
"article_number": "เช่น EAN, UPC, เป็นต้น",
"price": "ราคาของม้วนพลาสติกเต็ม (เส้นพลาสติกทั้งหมด)",
"weight": "น้ำหนักของเส้นพลาสติกในม้วนพลาสติก (สุทธิ) ไม่รวมม้วนพลาสติกเปล่า เป็นค่าน้ำหนักที่มักระบุบนบรรจุภัณฑ์",
"name": "ชื่อเส้นพลาสติก เพื่อแยกความต่างระหว่างเส้นพลาสติกแบบเดียวกันจากผู้ผลิตเดียวกัน โดยทั่วไปควรระบุสีในชื่อด้วย",
"multi_color_direction": "เส้นพลาสติกหลายสีสามารถมีหลายสีได้ 2 รูปแบบ: แบบ coextrusion (เช่น เส้นพลาสติกสองสีพร้อมกัน) หรือแบบไล่สีตามความยาว (เช่น เส้นพลาสติกเปลี่ยนสีไปเรื่อยๆ)"
},
"fields": {
"comment": "หมายเหตุ",
"density": "ความหนาแน่น",
"weight": "น้ำหนัก",
"vendor": "ผู้ผลิต",
"settings_extruder_temp": "อุณหภูมิหัวพิมพ์",
"longitudinal": "ไล่สีตามความยาว",
"material": "วัสดุ",
"id": "ID",
"settings_bed_temp": "อุณหภูมิฐานพิมพ์",
"vendor_name": "ยี่ห้อ",
"diameter": "เส้นผ่านศูนย์กลาง",
"article_number": "เลขบทความ",
"multi_color": "หลายสี",
"external_id": "รหัสภายนอก",
"color_hex": "สี",
"price": "ราคา",
"spool_weight": "น้ำหนักม้วนพลาสติกเปล่า",
"coaxial": "เส้นหลายสี",
"name": "ชื่อ",
"spools": "แสดงม้วนพลาสติก",
"registered": "ลงทะเบียน",
"single_color": "สีเดียว"
},
"titles": {
"edit": "แก้ไขเส้นพลาสติก",
"list": "รายการเส้นพลาสติก",
"show": "แสดงเส้นพลาสติก",
"clone": "ทำซ้ำเส้นพลาสติก",
"show_title": "[เส้นพลาสติก #{{id}}] {{name}}",
"create": "สร้างเส้นพลาสติก"
},
"form": {
"filament_updated": "เส้นพลาสติกนี้ได้รับการอัปเดตโดยผู้อื่นหลังจากที่คุณเปิดหน้านี้ การบันทึกจะเขียนทับการเปลี่ยนแปลง!",
"import_external": "นำเข้าจากภายนอก",
"import_external_description": "เลือกเส้นพลาสติกในรายการเพื่อกรอกข้อมูลอัตโนมัติในฟอร์มนี้ การดำเนินการนี้จะเขียนทับข้อมูลที่คุณกรอกไว้ อ็อบเจ็กต์ผู้ผลิตจะถูกสร้างขึ้นโดยอัตโนมัติเมื่อคุณคลิกตกลง"
},
"buttons": {
"add_spool": "เพิ่มม้วนพลาสติก"
},
"filament": "เส้นพลาสติก"
},
"kofi": "สนับสนุนเราได้ที่ Ko-fi",
"scanner": {
"error": {
"notReadable": "ไม่สามารถอ่านข้อมูลจากกล้องได้",
"insecureContext": "หน้านี้ไม่ได้ถูกให้บริการผ่าน HTTPS",
"notAllowed": "คุณไม่ได้อนุญาตให้เข้าถึงกล้อง",
"streamApiNotSupported": "เบราว์เซอร์ไม่รองรับ MediaStream API",
"unknown": "เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ ({{error}})",
"notFound": "ไม่พบกล้อง"
},
"description": "สแกน Spoolman QR code เพื่อดูข้อมูลเกี่ยวกับม้วนพลาสติก",
"title": "เครื่องสแกน QR Code"
},
"documentTitle": {
"filament": {
"clone": "#{{id}} ทำซ้ำเส้นพลาสติก | Spoolman",
"edit": "#{{id}} แก้ไขเส้นพลาสติก | Spoolman",
"show": "#{{id}} แสดงเส้นพลาสติก | Spoolman",
"create": "สร้างเส้นพลาสติก | Spoolman",
"list": "เส้นพลาสติก | Spoolman"
},
"home": {
"list": "หน้าแรก | Spoolman"
},
"spool": {
"show": "#{{id}} แสดงม้วนพลาสติก | Spoolman",
"clone": "#{{id}} ทำซ้ำม้วนพลาสติก | Spoolman",
"create": "สร้างม้วนพลาสติก | Spoolman",
"list": "ม้วนพลาสติก | Spoolman",
"edit": "#{{id}} แก้ไขม้วนพลาสติก | Spoolman"
},
"vendor": {
"list": "ผู้ผลิต | Spoolman",
"show": "#{{id}} แสดงผู้ผลิต | Spoolman",
"edit": "#{{id}} แก้ไขผู้ผลิต | Spoolman",
"create": "สร้างผู้ผลิต | Spoolman",
"clone": "#{{id}} ทำซ้ำผู้ผลิต | Spoolman"
},
"help": {
"list": "วิธีใช้ | Spoolman"
},
"suffix": " | Spoolman",
"locations": {
"list": "สถานที่เก็บ | Spoolman"
},
"default": "สปูลแมน"
},
"notifications": {
"error": "เกิดข้อผิดพลาด (รหัสสถานะ: {{statusCode}})",
"undoable": "คุณเหลือเวลา {{seconds}} วินาทีในการย้อนกลับ",
"deleteSuccess": "ลบ {{resource}} สำเร็จ",
"editSuccess": "แก้ไข {{resource}} สำเร็จ",
"validationError": "เกิดข้อผิดพลาดในการตรวจสอบ: {{error}}",
"deleteError": "เกิดข้อผิดพลาดในการลบ {{resource}} (รหัสสถานะ: {{statusCode}})",
"success": "สำเร็จ",
"createSuccess": "สร้าง {{resource}} สำเร็จ",
"saveSuccessful": "บันทึกสำเร็จ!",
"editError": "เกิดข้อผิดพลาดในการแก้ไข {{resource}} (รหัสสถานะ: {{statusCode}})",
"createError": "เกิดข้อผิดพลาดในการสร้าง {{resource}} (รหัสสถานะ: {{statusCode}})",
"importProgress": "กำลังนำเข้า: {{processed}}/{{total}}"
},
"loading": "กำลังโหลด",
"version": "เวอร์ชัน",
"unknown": "ไม่ทราบ",
"yes": "ใช่",
"no": "ไม่",
"tags": {
"clone": "ทำซ้ำ"
},
"vendor": {
"titles": {
"create": "สร้างผู้ผลิต/ยี่ห้อ",
"list": "รายการผู้ผลิต/ยี่ห้อ",
"clone": "ทำซ้ำผู้ผลิต/ยี่ห้อ",
"show_title": "[ผู้ผลิต #{{id}}] {{name}}",
"edit": "แก้ไขผู้ผลิต/ยี่ห้อ",
"show": "แสดงผู้ผลิต/ยี่ห้อ"
},
"fields": {
"empty_spool_weight": "น้ำหนักม้วนพลาสติกเปล่า",
"id": "ID",
"comment": "หมายเหตุ",
"external_id": "รหัสภายนอก",
"registered": "ลงทะเบียน",
"name": "ชื่อ"
},
"fields_help": {
"empty_spool_weight": "น้ำหนักม้วนพลาสติกเปล่าของผู้ผลิตนี้"
},
"form": {
"vendor_updated": "ผู้ผลิตนี้ถูกอัปเดตโดยผู้อื่นหลังจากที่คุณเปิดหน้านี้ การบันทึกจะเขียนทับการเปลี่ยนแปลง!"
},
"vendor": "ผู้ผลิต"
},
"help": {
"resources": {
"vendor": "บริษัทที่ผลิตเส้นพลาสติก",
"spool": "ม้วนพลาสติกจริงสำหรับเส้นพลาสติกเฉพาะ",
"filament": "แบรนด์เส้นพลาสติก มีคุณสมบัติต่างๆ เช่น ชื่อ วัสดุ สี เส้นผ่านศูนย์กลาง และอื่นๆ"
},
"help": "วิธีใช้",
"description": "<title>วิธีใช้</title><p>นี่คือเคล็ดลับบางส่วนเพื่อเริ่มต้น</p><p>Spoolman เก็บข้อมูลหลัก 3 ประเภท:</p><itemsHelp/><p>ในการเพิ่มม้วนพลาสติกใหม่ลงในฐานข้อมูล ให้เริ่มด้วยการสร้างอ็อบเจ็กต์ <filamentCreateLink>เส้นพลาสติก</filamentCreateLink> เมื่อเสร็จแล้วจึงสร้างอ็อบเจ็กต์ <spoolCreateLink>ม้วนพลาสติก</spoolCreateLink> สำหรับม้วนพลาสติกนั้น หากคุณได้ม้วนพลาสติกเพิ่มในภายหลัง ก็เพียงสร้างอ็อบเจ็กต์ม้วนพลาสติกใหม่และใช้วัตถุ เส้นพลาสติก เดิมได้</p><p>คุณสามารถสร้างอ็อบเจ็กต์ <vendorCreateLink>Manufacturer</vendorCreateLink> เพื่อเก็บข้อมูลบริษัทผู้ผลิตเส้นพลาสติกหากคุณต้องการติดตามข้อมูลนั้น</p><p>คุณสามารถเชื่อมต่อบริการ 3D printer อื่นๆ เข้ากับ Spoolman เช่น Moonraker เพื่อบันทึกปริมาณการใช้เส้นพลาสติกโดยอัตโนมัติและอัปเดตอ็อบเจ็กต์ม้วนพลาสติกของคุณ ดูเพิ่มเติมได้ที่ <readmeLink>Spoolman README</readmeLink></p>"
},
"dashboard": {
"title": "แดชบอร์ด"
},
"settings": {
"settings": "การตั้งค่า",
"header": "การตั้งค่า",
"general": {
"tab": "ทั่วไป",
"currency": {
"label": "สกุลเงิน"
},
"base_url": {
"label": "URLพื้นฐาน",
"tooltip": "URLพื้นฐาน ที่ใช้สำหรับสร้างฟีเจอร์ต่างๆ เช่น QR code"
}
},
"extra_fields": {
"params": {
"multi_choice": "เลือกหลายตัว",
"key": "คีย์",
"name": "ชื่อ",
"field_type": "ประเภท",
"unit": "หน่วย",
"order": "ลำดับ",
"default_value": "ค่าเริ่มต้น",
"choices": "ตัวเลือก"
},
"delete_confirm_description": "สิ่งนี้จะลบฟิลด์และข้อมูลที่เกี่ยวข้องทั้งหมดสำหรับเอนทิตี",
"field_type": {
"text": "ข้อความ",
"integer": "จำนวนเต็ม",
"integer_range": "ช่วงจำนวนเต็ม",
"float": "ทศนิยม",
"float_range": "ช่วงทศนิยม",
"datetime": "วันเวลา",
"boolean": "บูลีน(ใส่เฉพาะ ใช่/ไม่)",
"choice": "ตัวเลือก"
},
"tab": "ฟิลด์เพิ่มเติม",
"boolean_true": "ใช่",
"boolean_false": "ไม่",
"choices_missing_error": "คุณไม่สามารถลบตัวเลือกได้ ตัวเลือกต่อไปนี้หายไป: {{choices}}",
"non_unique_key_error": "คีย์ต้องไม่ซ้ำ",
"key_not_changed": "โปรดเปลี่ยนคีย์เป็นอย่างอื่น",
"delete_confirm": "ลบฟิลด์ {{name}} หรือไม่?",
"description": "<p>คุณสามารถเพิ่มฟิลด์ที่กำหนดเองเพิ่มเติมสำหรับเอนทิตีของคุณได้ที่นี่</p><p>เมื่อเพิ่มฟิลด์แล้ว คุณจะไม่สามารถเปลี่ยนคีย์หรือประเภทของฟิลด์ได้ และสำหรับฟิลด์ประเภทตัวเลือกคุณจะไม่สามารถลบตัวเลือกหรือเปลี่ยนโหมด multi choice ได้ หากคุณลบฟิลด์ ข้อมูลของเอนทิตีทั้งหมดจะถูกลบด้วย</p><p>คีย์คือสิ่งที่โปรแกรมอื่นจะใช้เพื่ออ่าน/เขียนข้อมูล ดังนั้นหากฟิลด์ที่คุณสร้างเพื่อเชื่อมต่อกับโปรแกรมอื่น กรุณาตั้งค่าให้ถูกต้อง ค่าดีฟอลต์จะถูกนำไปใช้เฉพาะกับรายการใหม่เท่านั้น</p><p>ฟิลด์เพิ่มเติมไม่สามารถเรียงหรือกรองได้ในตาราง</p>"
}
},
"locations": {
"new_location": "สถานที่เก็บใหม่",
"no_location": "ไม่มีสถานที่เก็บ",
"locations": "สถานที่เก็บ",
"no_locations_help": "หน้านี้ให้คุณจัดการม้วนพลาสติกตามสถานที่เก็บ เพิ่มม้วนพลาสติกเพื่อเริ่มต้น!"
},
"home": {
"home": "หน้าแรก"
},
"warnWhenUnsavedChanges": "คุณแน่ใจหรือไม่ว่าต้องการออก? คุณมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึก",
"table": {
"actions": "การดำเนินการ"
}
}

View File

@@ -218,6 +218,9 @@
"measurement_type_label": "测量类型",
"adjust_filament_help": "在这里你可以直接增加或减少线盘中的耗材。正数是减少耗材数量,负数是增加耗材数量。",
"adjust_filament_value": "消耗量"
},
"formats": {
"last_used": "上次使用 {{date}}"
}
},
"filament": {
@@ -329,6 +332,9 @@
},
"help": {
"list": "帮助 | Spoolman"
},
"locations": {
"list": "位置 | Spoolman"
}
},
"kofi": "在Ko-fi上给我打赏一点小费",
@@ -386,7 +392,17 @@
"base_url": {
"label": "基础URL",
"tooltip": "生成功能(例如二维码)时使用的基础 URL。"
},
"round_prices": {
"label": "取整",
"tooltip": "将价格四舍五入到最接近的整数。"
}
}
},
"locations": {
"no_locations_help": "此页面允许您在不同位置管理料盘,添加一些料盘即可开始!",
"locations": "位置",
"new_location": "新位置",
"no_location": "没有位置"
}
}

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env node
import { readdirSync, readFileSync, statSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const LOCALES_DIR = join(__dirname, "../public/locales");
const I18N_FILE = join(__dirname, "../src/i18n.ts");
const minLocaleFileSize = 1024 * 10; // Minimum 10kB for a locale file to be considered
function getLocaleFolders() {
return readdirSync(LOCALES_DIR).filter((folder) => {
const folderPath = join(LOCALES_DIR, folder);
const commonFilePath = join(folderPath, "common.json");
return (
statSync(folderPath).isDirectory() &&
statSync(commonFilePath).isFile() &&
statSync(commonFilePath).size >= minLocaleFileSize
);
});
}
function getDeclaredLanguages() {
const i18nContent = readFileSync(I18N_FILE, "utf8");
const languageMatches = [...i18nContent.matchAll(/\["(.*?)"\]:/g)];
return languageMatches.map((match) => match[1]);
}
function main() {
const foundLocales = new Set(getLocaleFolders());
const declaredLocales = new Set(getDeclaredLanguages());
const missingLocales = [...foundLocales].filter((locale) => !declaredLocales.has(locale));
if (missingLocales.length > 0) {
console.error("❌ The following locales are missing from src/i18n.ts:");
missingLocales.forEach((locale) => console.error(` - ${locale}`));
console.error("⚠️ Please add them to the `languages` object in i18n.ts.");
console.log("Template:");
for (const locale of missingLocales) {
console.log(`["${locale}"]: {
name: "",
fullCode: "",
djs: () => import("dayjs/locale/${locale.toLowerCase()}"),
},`);
}
process.exit(1);
}
console.log("✅ All locales are properly declared in i18n.ts.");
process.exit(0);
}
main();

View File

@@ -15,12 +15,12 @@ import {
UserOutlined,
} from "@ant-design/icons";
import loadable from "@loadable/component";
import routerBindings, { DocumentTitleHandler, UnsavedChangesNotifier } from "@refinedev/react-router-v6";
import routerBindings, { DocumentTitleHandler, UnsavedChangesNotifier } from "@refinedev/react-router";
import { ConfigProvider } from "antd";
import { Locale } from "antd/es/locale";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom";
import { BrowserRouter, Outlet, Route, Routes } from "react-router";
import dataProvider from "./components/dataProvider";
import { Favicon } from "./components/favicon";
import { SpoolmanLayout } from "./components/layout";

View File

@@ -5,7 +5,7 @@ import { ColumnFilterItem, ColumnType } from "antd/es/table/interface";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { AlignType } from "rc-table/lib/interface";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { getFiltersForField, typeFilters } from "../utils/filtering";
import { enrichText } from "../utils/parsing";
import { Field, FieldType } from "../utils/queryFields";
@@ -111,11 +111,13 @@ function Column<Obj extends Entity>(
if (props.loadingFilters) {
columnProps.filterDropdown = <FilterDropdownLoading />;
}
columnProps.onFilterDropdownOpenChange = (open) => {
columnProps.filterDropdownProps = {
onOpenChange: (open) => {
if (open && props.onFilterDropdownOpen) {
props.onFilterDropdownOpen();
}
};
}
}
if (props.dataId) {
columnProps.key = props.dataId;
}

View File

@@ -7,7 +7,6 @@ import { ColorModeContext } from "../../contexts/color-mode";
import { languages } from "../../i18n";
import QRCodeScannerModal from "../qrCodeScanner";
import "/node_modules/flag-icons/css/flag-icons.min.css";
const { useToken } = theme;
@@ -22,7 +21,6 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({ sticky }) =>
const menuItems: MenuProps["items"] = [...(Object.keys(languages) || [])].sort().map((lang: string) => ({
key: lang,
onClick: () => changeLanguage(lang),
icon: <span className={"fi fi-" + languages[lang].countryCode} style={{ marginRight: 8 }} />,
label: languages[lang].name,
}));
@@ -52,7 +50,6 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({ sticky }) =>
>
<Button type="text">
<Space>
<span className={"fi fi-" + languages[currentLocale ?? "en"].countryCode} />
{languages[currentLocale ?? "en"].name}
<DownOutlined />
</Space>

View File

@@ -3,7 +3,7 @@ import { useTranslate } from "@refinedev/core";
import { QrScanner } from "@yudiel/react-qr-scanner";
import { FloatButton, Modal, Space } from "antd";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
const QRCodeScannerModal: React.FC = () => {
const [visible, setVisible] = useState(false);

View File

@@ -15,6 +15,10 @@
height: 4em;
}
.spool-icon.no-margin {
margin: 0;
}
.spool-icon * {
flex: 1 1 0px;
border-radius: 2px;

View File

@@ -3,12 +3,14 @@ import "./spoolIcon.css";
interface Props {
color: string | { colors: string[]; vertical: boolean };
size?: "small" | "large";
no_margin? : boolean
}
export default function SpoolIcon(props: Props) {
export default function SpoolIcon(props: Readonly<Props>) {
let dirClass = "vertical";
let cols = [];
let size = props.size ? props.size : "small";
let no_margin = props.no_margin ? "no-margin" : "";
if (typeof props.color === "string") {
cols = [props.color];
@@ -18,7 +20,7 @@ export default function SpoolIcon(props: Props) {
}
return (
<div className={"spool-icon " + dirClass + " " + size}>
<div className={"spool-icon " + dirClass + " " + size + " " + no_margin}>
{cols.map((col) => (
<div
key={col}

View File

@@ -7,7 +7,6 @@ import { getBasePath } from "./utils/url";
interface Language {
name: string;
countryCode: string;
fullCode: string;
djs: () => Promise<ILocale>;
}
@@ -16,131 +15,130 @@ interface Language {
* List of languages to load
* The key of each object is the folder name in the locales dir.
* name: Name of the language in the list
* countryCode: Country code of the country's flag to display for this language
* fullCode: Full language code, used for Ant Design's locale
* djs: Function to load the dayjs locale, see https://github.com/iamkun/dayjs/tree/dev/src/locale for list of locales
*/
export const languages: { [key: string]: Language } = {
["en"]: {
name: "English",
countryCode: "gb",
fullCode: "en-GB",
djs: () => import("dayjs/locale/en"),
},
["sv"]: {
name: "Svenska",
countryCode: "se",
fullCode: "sv-SE",
djs: () => import("dayjs/locale/sv"),
},
["de"]: {
name: "Deutsch",
countryCode: "de",
fullCode: "de-DE",
djs: () => import("dayjs/locale/de"),
},
["es"]: {
name: "Español",
countryCode: "es",
fullCode: "es-ES",
djs: () => import("dayjs/locale/es"),
},
["zh"]: {
name: "简体中文",
countryCode: "cn",
fullCode: "zh-CN",
djs: () => import("dayjs/locale/zh-cn"),
},
["zh-Hant"]: {
name: "繁體中文",
countryCode: "cn",
fullCode: "zh-TW",
djs: () => import("dayjs/locale/zh-hk"),
},
["pl"]: {
name: "Polski",
countryCode: "pl",
fullCode: "pl-PL",
djs: () => import("dayjs/locale/pl"),
},
["ru"]: {
name: "Русский",
countryCode: "ru",
fullCode: "ru-RU",
djs: () => import("dayjs/locale/ru"),
},
["cs"]: {
name: "Česky",
countryCode: "cz",
fullCode: "cs-CZ",
djs: () => import("dayjs/locale/cs"),
},
["nb-NO"]: {
name: "Norsk bokmål",
countryCode: "no",
fullCode: "nb-NO",
djs: () => import("dayjs/locale/nb"),
},
["nl"]: {
name: "Nederlands",
countryCode: "nl",
fullCode: "nl-NL",
djs: () => import("dayjs/locale/nl"),
},
["fr"]: {
name: "Français",
countryCode: "fr",
fullCode: "fr-FR",
djs: () => import("dayjs/locale/fr"),
},
["hu"]: {
name: "Magyar",
countryCode: "hu",
fullCode: "hu-HU",
djs: () => import("dayjs/locale/hu"),
},
["it"]: {
name: "Italiano",
countryCode: "it",
fullCode: "it-IT",
djs: () => import("dayjs/locale/it"),
},
["uk"]: {
name: "Українська",
countryCode: "ua",
fullCode: "uk-UA",
djs: () => import("dayjs/locale/uk"),
},
["el"]: {
name: "Ελληνικά",
countryCode: "gr",
fullCode: "el-GR",
djs: () => import("dayjs/locale/el"),
},
["da"]: {
name: "Dansk",
countryCode: "dk",
fullCode: "da-DK",
djs: () => import("dayjs/locale/da"),
},
["pt"]: {
name: "Português",
countryCode: "pt",
fullCode: "pt-PT",
djs: () => import("dayjs/locale/pt"),
},
["fa"]: {
name: "فارسی",
countryCode: "ir",
fullCode: "fa-IR",
djs: () => import("dayjs/locale/fa"),
},
["ro"]: {
name: "Român",
countryCode: "ro",
fullCode: "ro-RO",
djs: () => import("dayjs/locale/ro"),
},
["ja"]: {
name: "日本語",
fullCode: "ja-JP",
djs: () => import("dayjs/locale/ja"),
},
["pt-BR"]: {
name: "Português (Brasil)",
fullCode: "pt-BR",
djs: () => import("dayjs/locale/pt-br"),
},
["ta"]: {
name: "தமிழ்",
fullCode: "ta-IN",
djs: () => import("dayjs/locale/ta"),
},
["th"]: {
name: "ไทย",
fullCode: "th-TH",
djs: () => import("dayjs/locale/th"),
},
};
i18n

View File

@@ -1,3 +1,4 @@
import '@ant-design/v5-patch-for-react-19';
import React from "react";
import { createRoot } from "react-dom/client";

View File

@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { FilamentImportModal } from "../../components/filamentImportModal";
import { MultiColorPicker } from "../../components/multiColorPicker";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { ExternalFilament } from "../../utils/queryExternalDB";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
@@ -250,7 +250,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
<InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty}
/>
</Form.Item>
@@ -266,7 +266,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
},
]}
>
<InputNumber addonAfter="g/cm³" precision={2} formatter={numberFormatter} parser={numberParser} />
<InputNumber addonAfter="g/cm³" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item>
<Form.Item
label={t("filament.fields.diameter")}
@@ -280,7 +280,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
},
]}
>
<InputNumber addonAfter="mm" precision={2} formatter={numberFormatter} parser={numberParser} />
<InputNumber addonAfter="mm" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item>
<Form.Item
label={t("filament.fields.weight")}

View File

@@ -6,7 +6,7 @@ import dayjs from "dayjs";
import React, { useEffect, useState } from "react";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { MultiColorPicker } from "../../components/multiColorPicker";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { IVendor } from "../vendors/model";
@@ -63,6 +63,9 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
const originalOnFinish = formProps.onFinish;
formProps.onFinish = (allValues: IFilamentParsedExtras) => {
if (allValues !== undefined && allValues !== null) {
if (colorType == "single") {
allValues.multi_color_hexes = "";
}
// Lot of stupidity here to make types work
const stringifiedAllValues = StringifiedExtras<IFilamentParsedExtras>(allValues);
originalOnFinish?.({
@@ -166,7 +169,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
return e?.toHex();
}}
>
<ColorPicker format="hex" />
<ColorPicker />
</Form.Item>
)}
{colorType == "multi" && (
@@ -225,7 +228,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
<InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty}
/>
</Form.Item>
@@ -241,7 +244,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
},
]}
>
<InputNumber addonAfter="g/cm³" precision={2} formatter={numberFormatter} parser={numberParser} />
<InputNumber addonAfter="g/cm³" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item>
<Form.Item
label={t("filament.fields.diameter")}
@@ -255,7 +258,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
},
]}
>
<InputNumber addonAfter="mm" precision={2} formatter={numberFormatter} parser={numberParser} />
<InputNumber addonAfter="mm" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
</Form.Item>
<Form.Item
label={t("filament.fields.weight")}

View File

@@ -5,7 +5,7 @@ import { Button, Dropdown, Table } from "antd";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import {
ActionsColumn,
CustomFieldColumn,
@@ -26,7 +26,7 @@ import {
import { removeUndefined } from "../../utils/filtering";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
import { useCurrency } from "../../utils/settings";
import { useCurrencyFormatter } from "../../utils/settings";
import { IFilament } from "./model";
dayjs.extend(utc);
@@ -77,7 +77,7 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
const invalidate = useInvalidate();
const navigate = useNavigate();
const extraFields = useGetFields(EntityType.filament);
const currency = useCurrency();
const currencyFormatter = useCurrencyFormatter();
const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
@@ -263,12 +263,10 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
align: "right",
width: 80,
render: (_, obj: IFilamentCollapsed) => {
return obj.price?.toLocaleString(undefined, {
style: "currency",
currencyDisplay: "narrowSymbol",
currency: currency,
notation: "compact",
});
if (obj.price === undefined) {
return "";
}
return currencyFormatter.format(obj.price);
},
}),
NumberColumn({

View File

@@ -4,13 +4,13 @@ import { Button, Typography } from "antd";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import React from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { ExtraFieldDisplay } from "../../components/extraFields";
import { NumberFieldUnit } from "../../components/numberField";
import SpoolIcon from "../../components/spoolIcon";
import { enrichText } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { useCurrency } from "../../utils/settings";
import { useCurrencyFormatter } from "../../utils/settings";
import { IFilament } from "./model";
dayjs.extend(utc);
@@ -20,7 +20,7 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate();
const navigate = useNavigate();
const extraFields = useGetFields(EntityType.filament);
const currency = useCurrency();
const currencyFormatter = useCurrencyFormatter();
const { queryResult } = useShow<IFilament>({
liveMode: "auto",
});
@@ -88,19 +88,12 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
<Title level={5}>{t("filament.fields.name")}</Title>
<TextField value={record?.name} />
<Title level={5}>{t("filament.fields.color_hex")}</Title>
{colorObj && <SpoolIcon color={colorObj} size="large" />}
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin />}
{record?.color_hex && <TextField value={`#${record?.color_hex}`} />}
<Title level={5}>{t("filament.fields.material")}</Title>
<TextField value={record?.material} />
<Title level={5}>{t("filament.fields.price")}</Title>
<NumberField
value={record?.price ?? ""}
options={{
style: "currency",
currency: currency,
currencyDisplay: "narrowSymbol",
notation: "compact",
}}
/>
<TextField value={record?.price ? currencyFormatter.format(record.price) : ""} />
<Title level={5}>{t("filament.fields.density")}</Title>
<NumberFieldUnit
value={record?.density ?? ""}

View File

@@ -7,7 +7,7 @@ import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import React from "react";
import { Trans } from "react-i18next";
import { Link } from "react-router-dom";
import { Link } from "react-router";
dayjs.extend(utc);

View File

@@ -6,7 +6,7 @@ import Title from "antd/es/typography/Title";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import React, { ReactNode } from "react";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import Logo from "../../icon.svg?react";
import { ISpool } from "../spools/model";

View File

@@ -8,7 +8,7 @@ import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import utc from "dayjs/plugin/utc";
import { useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import SpoolIcon from "../../../components/spoolIcon";
import { formatWeight } from "../../../utils/parsing";
import { ISpool } from "../../spools/model";

View File

@@ -5,7 +5,7 @@ import { Content } from "antd/es/layout/layout";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import React from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router";
import SpoolQRCodePrintingDialog from "./spoolQrCodePrintingDialog";
import SpoolSelectModal from "./spoolSelectModal";

View File

@@ -1,3 +1,4 @@
import { ReactElement } from "react";
import { v4 as uuidv4 } from "uuid";
import { useGetSetting, useSetSetting } from "../../utils/querySettings";
import { ISpool } from "../spools/model";
@@ -75,7 +76,7 @@ function getTagValue(tag: string, obj: GenericObject): any {
return value;
}
function applyNewline(text: string): JSX.Element[] {
function applyNewline(text: string): ReactElement[] {
return text.split("\n").map((line, idx, arr) => (
<span key={idx}>
{line}
@@ -84,7 +85,7 @@ function applyNewline(text: string): JSX.Element[] {
));
}
function applyTextFormatting(text: string): JSX.Element[] {
function applyTextFormatting(text: string): ReactElement[] {
const regex = /\*\*([\w\W]*?)\*\*/g;
const parts = text.split(regex);
// Map over the parts and wrap matched text with <b> tags
@@ -96,7 +97,7 @@ function applyTextFormatting(text: string): JSX.Element[] {
return elements;
}
export function renderLabelContents(template: string, spool: ISpool): JSX.Element {
export function renderLabelContents(template: string, spool: ISpool): ReactElement {
// Find all {tags} in the template string and loop over them
// let matches = [...template.matchAll(/(?:{(.*?))?{(.*?)}(.*?)(?:}(.*?))?/gs)];
let matches = [...template.matchAll(/{(?:[^}{]|{[^}{]*})*}/gs)];

View File

@@ -15,19 +15,19 @@ import {
Space,
} from "antd";
import * as htmlToImage from "html-to-image";
import React, { useRef } from "react";
import ReactToPrint from "react-to-print";
import React, { ReactElement, useRef } from "react";
import { useReactToPrint } from "react-to-print";
import { useSavedState } from "../../utils/saveload";
import { PrintSettings } from "./printing";
interface PrintingDialogProps {
items: JSX.Element[];
items: ReactElement[];
printSettings: PrintSettings;
setPrintSettings: (setPrintSettings: PrintSettings) => void;
style?: string;
extraSettings?: JSX.Element;
extraSettingsStart?: JSX.Element;
extraButtons?: JSX.Element;
extraSettings?: ReactElement;
extraSettingsStart?: ReactElement;
extraButtons?: ReactElement;
}
interface PaperDimensions {
@@ -90,7 +90,8 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
const paperWidth = paperSize === "custom" ? customPaperSize.width : paperDimensions[paperSize].width;
const paperHeight = paperSize === "custom" ? customPaperSize.height : paperDimensions[paperSize].height;
const printRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const reactToPrintFn = useReactToPrint({ contentRef });
const itemWidth = (paperWidth - margin.left - margin.right - spacing.horizontal) / paperColumns - spacing.horizontal;
const itemHeight = (paperHeight - margin.top - margin.bottom - spacing.vertical) / paperRows - spacing.vertical;
@@ -223,7 +224,7 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
>
<div
className="print-container"
ref={printRef}
ref={contentRef}
style={{
transform: `scale(${previewScale})`,
transformOrigin: "top left",
@@ -817,15 +818,9 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
<Button type="primary" icon={<FileImageOutlined />} size="large" onClick={saveAsImage}>
{t("printing.generic.saveAsImage")}
</Button>
<ReactToPrint
key="print-button"
trigger={() => (
<Button type="primary" icon={<PrinterOutlined />} size="large">
<Button type="primary" icon={<PrinterOutlined />} size="large" onClick={() => reactToPrintFn()}>
{t("printing.generic.print")}
</Button>
)}
content={() => printRef.current}
/>
</Space>
</Col>
</Row>

View File

@@ -1,5 +1,7 @@
import { useTranslate } from "@refinedev/core";
import { Col, Form, InputNumber, QRCode, Radio, RadioChangeEvent, Row, Slider, Switch, Typography } from "antd";
import { ReactElement } from "react";
import { getBasePath } from "../../utils/url";
import { QRCodePrintSettings } from "./printing";
import PrintingDialog from "./printingDialog";
@@ -7,7 +9,7 @@ const { Text } = Typography;
interface QRCodeData {
value: string;
label?: JSX.Element;
label?: ReactElement;
errorLevel?: "L" | "M" | "Q" | "H";
}
@@ -15,9 +17,9 @@ interface QRCodePrintingDialogProps {
items: QRCodeData[];
printSettings: QRCodePrintSettings;
setPrintSettings: (setPrintSettings: QRCodePrintSettings) => void;
extraSettings?: JSX.Element;
extraSettingsStart?: JSX.Element;
extraButtons?: JSX.Element;
extraSettings?: ReactElement;
extraSettingsStart?: ReactElement;
extraButtons?: ReactElement;
baseUrlRoot: string;
useHTTPUrl: boolean;
setUseHTTPUrl: (value: boolean) => void;
@@ -47,7 +49,7 @@ const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
<div className="print-qrcode-container">
<QRCode
className="print-qrcode"
icon={showQRCodeMode === "withIcon" ? "/favicon.svg" : undefined}
icon={showQRCodeMode === "withIcon" ? getBasePath() + "/favicon.svg" : undefined}
value={item.value}
errorLevel={item.errorLevel}
type="svg"

View File

@@ -3,7 +3,7 @@ import { useTable } from "@refinedev/antd";
import { Button, Checkbox, Col, message, Row, Space, Table } from "antd";
import { t } from "i18next";
import { useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { FilteredQueryColumn, SortedColumn, SpoolIconColumn } from "../../components/column";
import { useSpoolmanFilamentFilter, useSpoolmanMaterials } from "../../components/otherModels";
import { removeUndefined } from "../../utils/filtering";

View File

@@ -22,7 +22,7 @@ import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { useState } from "react";
import { Trans } from "react-i18next";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import { DateTimePicker } from "../../components/dateTimePicker";
import { InputNumberRange } from "../../components/inputNumberRange";
import { EntityType, Field, FieldType, useDeleteField, useGetFields, useSetField } from "../../utils/queryFields";
@@ -488,10 +488,16 @@ export function ExtraFieldsSettings() {
} else if (Array.isArray(val) && record.field.field_type === FieldType.choice) {
return val.join(", ");
} else if (
(Array.isArray(val) && record.field.field_type === FieldType.integer_range) ||
record.field.field_type === FieldType.float_range
Array.isArray(val) && (
record.field.field_type === FieldType.integer_range || record.field.field_type === FieldType.float_range
)
) {
return `${val[0]} \u2013 ${val[1]}`;
let lower = val[0] ?? "";
let upper = val[1] ?? "";
if (lower === "" && upper === "") {
return null;
}
return `${lower} \u2013 ${upper}`;
} else {
return null;
}

View File

@@ -1,5 +1,5 @@
import { useTranslate } from "@refinedev/core";
import { Button, Form, Input, message } from "antd";
import { Button, Checkbox, Form, Input, message } from "antd";
import { useEffect } from "react";
import { useGetSettings, useSetSetting } from "../../utils/querySettings";
@@ -7,6 +7,7 @@ export function GeneralSettings() {
const settings = useGetSettings();
const setBaseUrl = useSetSetting("base_url");
const setCurrency = useSetSetting("currency");
const setRoundPrices = useSetSetting("round_prices");
const [form] = Form.useForm();
const [messageApi, contextHolder] = message.useMessage();
const t = useTranslate();
@@ -17,6 +18,7 @@ export function GeneralSettings() {
form.setFieldsValue({
currency: JSON.parse(settings.data.currency.value),
base_url: JSON.parse(settings.data.base_url.value),
round_prices: JSON.parse(settings.data.round_prices.value),
});
}
}, [settings.data, form]);
@@ -29,7 +31,7 @@ export function GeneralSettings() {
}, [setCurrency.isSuccess, messageApi, t]);
// Handle form submit
const onFinish = (values: { currency: string; base_url: string }) => {
const onFinish = (values: { currency: string; base_url: string, round_prices: boolean }) => {
// Check if the currency has changed
if (settings.data?.currency.value !== JSON.stringify(values.currency)) {
setCurrency.mutate(values.currency);
@@ -38,16 +40,21 @@ export function GeneralSettings() {
if (settings.data?.base_url.value !== JSON.stringify(values.base_url)) {
setBaseUrl.mutate(values.base_url);
}
// Check if the setting to round prices has changed
if (settings.data?.round_prices.value !== JSON.stringify(values.round_prices)) {
setRoundPrices.mutate(values.round_prices);
}
};
return (
<>
return (<>
<Form
form={form}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
initialValues={{
currency: settings.data?.currency.value,
round_prices: settings.data?.round_prices.value,
}}
onFinish={onFinish}
style={{
@@ -86,6 +93,15 @@ export function GeneralSettings() {
<Input placeholder="https://example.com:8000" />
</Form.Item>
<Form.Item
label={t("settings.general.round_prices.label")}
tooltip={t("settings.general.round_prices.tooltip")}
name="round_prices"
valuePropName="checked"
>
<Checkbox />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit" loading={settings.isFetching || setCurrency.isLoading}>
{t("buttons.save")}
@@ -93,6 +109,5 @@ export function GeneralSettings() {
</Form.Item>
</Form>
{contextHolder}
</>
);
</>);
}

View File

@@ -5,7 +5,7 @@ import { Content } from "antd/es/layout/layout";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import React from "react";
import { Route, Routes, useNavigate } from "react-router-dom";
import { Route, Routes, useNavigate } from "react-router";
import { ExtraFieldsSettings } from "./extraFieldsSettings";
import { GeneralSettings } from "./generalSettings";

View File

@@ -10,7 +10,7 @@ import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../compo
import { useSpoolmanLocations } from "../../components/otherModels";
import { searchMatches } from "../../utils/filtering";
import "../../utils/overrides.css";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { createFilamentFromExternal } from "../filaments/functions";
@@ -330,7 +330,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
<InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty}
/>
</Form.Item>
@@ -391,7 +391,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
min={0}
addonAfter="g"
precision={1}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParser}
disabled={weightToEnter != WeightToEnter.used_weight}
value={usedWeight}
@@ -409,7 +409,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
min={0}
addonAfter="g"
precision={1}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParser}
disabled={weightToEnter != WeightToEnter.remaining_weight}
value={getRemainingWeight()}
@@ -427,7 +427,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
min={0}
addonAfter="g"
precision={1}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParser}
disabled={weightToEnter != WeightToEnter.measured_weight}
value={getMeasuredWeight()}

View File

@@ -5,11 +5,11 @@ import TextArea from "antd/es/input/TextArea";
import { message } from "antd/lib";
import dayjs from "dayjs";
import { useEffect, useMemo, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { useSpoolmanLocations } from "../../components/otherModels";
import { searchMatches } from "../../utils/filtering";
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { createFilamentFromExternal } from "../filaments/functions";
@@ -320,7 +320,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
<InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParserAllowEmpty}
/>
</Form.Item>
@@ -380,7 +380,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
min={0}
addonAfter="g"
precision={1}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParser}
disabled={weightToEnter != WeightToEnter.used_weight}
value={usedWeight}
@@ -394,7 +394,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
min={0}
addonAfter="g"
precision={1}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParser}
disabled={weightToEnter != WeightToEnter.remaining_weight}
value={getRemainingWeight()}
@@ -408,7 +408,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
min={0}
addonAfter="g"
precision={1}
formatter={numberFormatter}
formatter={formatNumberOnUserInput}
parser={numberParser}
disabled={weightToEnter != WeightToEnter.measured_weight}
value={getMeasuredWeight()}

View File

@@ -2,7 +2,7 @@ import { useSelect, useTranslate } from "@refinedev/core";
import { useQueries } from "@tanstack/react-query";
import { Form, InputNumber, Modal, Radio } from "antd";
import { useForm } from "antd/es/form/Form";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { formatLength, formatWeight } from "../../utils/parsing";
import { SpoolType, useGetExternalDBFilaments } from "../../utils/queryExternalDB";
import { getAPIURL } from "../../utils/url";
@@ -44,6 +44,26 @@ export async function useSpoolFilament(spool: ISpool, length?: number, weight?:
await fetch(request, init);
}
/**
* Adjust usage based on the spool's current gross weight
* @param spool The spool
* @param weight The weight of the spool, in g
*/
export async function useSpoolFilamentMeasure(spool: ISpool, weight: number) {
const init: RequestInit = {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
weight: weight,
}),
};
const request = new Request(`${getAPIURL()}/spool/${spool.id}/measure`);
await fetch(request, init);
}
/**
* Returns an array of queries using the useQueries hook from @tanstack/react-query.
* Each query fetches a spool by its ID from the server.
@@ -173,7 +193,7 @@ export function useGetFilamentSelectOptions() {
};
}
type MeasurementType = "length" | "weight";
type MeasurementType = "length" | "weight" | "measured_weight";
export function useSpoolAdjustModal() {
const t = useTranslate();
@@ -181,9 +201,13 @@ export function useSpoolAdjustModal() {
const [curSpool, setCurSpool] = useState<ISpool | null>(null);
const [measurementType, setMeasurementType] = useState<MeasurementType>("length");
const inputNumberRef = useRef<HTMLInputElement | null>(null);
const openSpoolAdjustModal = useCallback((spool: ISpool) => {
setCurSpool(spool);
setTimeout(() => {
inputNumberRef.current?.focus();
}, 0);
}, []);
const spoolAdjustModal = useMemo(() => {
@@ -203,8 +227,10 @@ export function useSpoolAdjustModal() {
if (measurementType === "length") {
await useSpoolFilament(curSpool, value, undefined);
} else {
} else if (measurementType === "weight") {
await useSpoolFilament(curSpool, undefined, value);
} else {
await useSpoolFilamentMeasure(curSpool, value);
}
setCurSpool(null);
@@ -221,10 +247,11 @@ export function useSpoolAdjustModal() {
>
<Radio.Button value="length">{t("spool.form.measurement_type.length")}</Radio.Button>
<Radio.Button value="weight">{t("spool.form.measurement_type.weight")}</Radio.Button>
<Radio.Button value="measured_weight">{t("spool.fields.measured_weight")}</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label={t("spool.form.adjust_filament_value")} name="filament_value">
<InputNumber precision={1} addonAfter={measurementType === "length" ? "mm" : "g"} />
<InputNumber ref={inputNumberRef} precision={1} addonAfter={measurementType === "length" ? "mm" : "g"} />
</Form.Item>
</Form>
</Modal>

View File

@@ -14,7 +14,7 @@ import { Button, Dropdown, Modal, Table } from "antd";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { useCallback, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import {
Action,
ActionsColumn,
@@ -36,7 +36,7 @@ import {
import { removeUndefined } from "../../utils/filtering";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { TableState, useInitialTableState, useSavedState, useStoreInitialState } from "../../utils/saveload";
import { useCurrency } from "../../utils/settings";
import { useCurrencyFormatter } from "../../utils/settings";
import { setSpoolArchived, useSpoolAdjustModal } from "./functions";
import { ISpool } from "./model";
@@ -102,7 +102,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
const invalidate = useInvalidate();
const navigate = useNavigate();
const extraFields = useGetFields(EntityType.spool);
const currency = useCurrency();
const currencyFormatter = useCurrencyFormatter();
const { openSpoolAdjustModal, spoolAdjustModal } = useSpoolAdjustModal();
const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
@@ -381,12 +381,10 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
align: "right",
width: 80,
render: (_, obj: ISpoolCollapsed) => {
return obj.price?.toLocaleString(undefined, {
style: "currency",
currencyDisplay: "narrowSymbol",
currency: currency,
notation: "compact",
});
if (obj.price === undefined) {
return "";
}
return currencyFormatter.format(obj.price);
},
}),
NumberColumn({

View File

@@ -1,4 +1,5 @@
import { InboxOutlined, PrinterOutlined, ToTopOutlined } from "@ant-design/icons";
import { getBasePath } from "../../utils/url";
import { InboxOutlined, PrinterOutlined, ToTopOutlined, ToolOutlined } from "@ant-design/icons";
import { DateField, NumberField, Show, TextField } from "@refinedev/antd";
import { IResourceComponentsProps, useInvalidate, useShow, useTranslate } from "@refinedev/core";
import { Button, Modal, Typography } from "antd";
@@ -10,9 +11,9 @@ import { NumberFieldUnit } from "../../components/numberField";
import SpoolIcon from "../../components/spoolIcon";
import { enrichText } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { useCurrency } from "../../utils/settings";
import { useCurrencyFormatter } from "../../utils/settings";
import { IFilament } from "../filaments/model";
import { setSpoolArchived } from "./functions";
import { setSpoolArchived, useSpoolAdjustModal } from "./functions";
import { ISpool } from "./model";
dayjs.extend(utc);
@@ -23,7 +24,7 @@ const { confirm } = Modal;
export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate();
const extraFields = useGetFields(EntityType.spool);
const currency = useCurrency();
const currencyFormatter = useCurrencyFormatter();
const invalidate = useInvalidate();
const { queryResult } = useShow<ISpool>({
@@ -33,15 +34,17 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
const record = data?.data;
const spoolPrice = (item: ISpool) => {
let spoolPrice = "";
if (item.price === undefined) {
spoolPrice = `${item.filament.price}`;
return spoolPrice;
const spoolPrice = (item?: ISpool) => {
const price = item?.price ?? item?.filament.price;
if (price === undefined) {
return "";
}
return item.price;
return currencyFormatter.format(price);
};
// Provides the function to open the spool adjustment modal and the modal component itself
const { openSpoolAdjustModal, spoolAdjustModal } = useSpoolAdjustModal();
// Function for opening an ant design modal that asks for confirmation for archiving a spool
const archiveSpool = async (spool: ISpool, archive: boolean) => {
await setSpoolArchived(spool, archive);
@@ -115,10 +118,17 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
title={record ? formatTitle(record) : ""}
headerButtons={({ defaultButtons }) => (
<>
<Button
type="primary"
icon={<ToolOutlined />}
onClick={() => record && openSpoolAdjustModal(record)}
>
{t("spool.titles.adjust")}
</Button>
<Button
type="primary"
icon={<PrinterOutlined />}
href={"/spool/print?spools=" + record?.id + "&return=" + encodeURIComponent(window.location.pathname)}
href={getBasePath() + "/spool/print?spools=" + record?.id + "&return=" + encodeURIComponent(window.location.pathname)}
>
{t("printing.qrcode.button")}
</Button>
@@ -133,23 +143,17 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
)}
{defaultButtons}
{spoolAdjustModal}
</>
)}
>
<Title level={5}>{t("spool.fields.id")}</Title>
<NumberField value={record?.id ?? ""} />
<Title level={5}>{t("spool.fields.filament")}</Title>
{colorObj && <SpoolIcon color={colorObj} />} <TextField value={record ? filamentURL(record?.filament) : ""} />
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin />}
<TextField value={record ? filamentURL(record?.filament) : ""} />
<Title level={5}>{t("spool.fields.price")}</Title>
<NumberField
value={record ? spoolPrice(record) : ""}
options={{
style: "currency",
currency: currency,
currencyDisplay: "narrowSymbol",
notation: "compact",
}}
/>
<TextField value={spoolPrice(record)} />
<Title level={5}>{t("spool.fields.registered")}</Title>
<DateField
value={dayjs.utc(record?.registered).local()}

View File

@@ -5,7 +5,7 @@ import { Button, Dropdown, Table } from "antd";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { useCallback, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import {
ActionsColumn,
CustomFieldColumn,

View File

@@ -24,6 +24,22 @@ export function formatNumberWithSpaceSeparator(input: string): string {
return formattedNumber;
}
/**
* Number formatter compatible with Ant Design's InputNumber component, handling UX properly.
* @param value
* @param info
* @returns
*/
export function formatNumberOnUserInput(
value: number | string | undefined,
info: { userTyping: boolean; input: string }
): string {
if (info.userTyping) {
return info.input;
}
return numberFormatter(value);
}
/**
* Number formatter that nicely formats numbers with correct decimal separator based on locale
* Always uses blank space as thousands separator to prevent confusion with the decimal separator
@@ -31,7 +47,6 @@ export function formatNumberWithSpaceSeparator(input: string): string {
* @returns
*/
export function numberFormatter(value: number | string | undefined): string {
console.log("numberformatter input: ", value);
const formattedValue = value
? Number(value).toLocaleString(undefined, {
useGrouping: false, // Disable thousands separator and do it manually instead so it's always spaces
@@ -117,10 +132,10 @@ export function enrichText(text: string | undefined) {
*/
export function formatWeight(weightInGrams: number, precision: number = 2): string {
if (weightInGrams >= 1000) {
const kilograms = (weightInGrams / 1000).toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
const kilograms = removeTrailingZeros((weightInGrams / 1000).toFixed(precision));
return `${kilograms} kg`;
} else {
const grams = weightInGrams.toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
const grams = removeTrailingZeros(weightInGrams.toFixed(precision));
return `${grams} g`;
}
}
@@ -134,9 +149,30 @@ export function formatWeight(weightInGrams: number, precision: number = 2): stri
*/
export function formatLength(lengthInMillimeter: number, precision: number = 2): string {
if (lengthInMillimeter >= 1000) {
const meters = (lengthInMillimeter / 1000).toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
const meters = removeTrailingZeros((lengthInMillimeter / 1000).toFixed(precision));
return `${meters} m`;
} else {
return `${lengthInMillimeter} mm`;
}
}
/**
* Removes trailing zeros from a numeric string, including unnecessary decimal points.
*
* This function takes a string representation of a number and removes any trailing zeros
* after the decimal point. If the number ends with a decimal point followed by only zeros,
* the entire decimal portion is removed.
*
* @param num - The numeric string to process.
* @returns The numeric string with trailing zeros removed.
*
* @example
* ```typescript
* removeTrailingZeros("123.45000"); // Returns "123.45"
* removeTrailingZeros("100.000"); // Returns "100"
* removeTrailingZeros("0.0000"); // Returns "0"
* ```
*/
function removeTrailingZeros(num: string): string {
return num.replace(/(\.\d*?[1-9])0+|\.0*$/, "$1");
}

View File

@@ -17,3 +17,15 @@ export function getCurrencySymbol(locale: string | undefined, currency: string)
.replace(/\d/g, "")
.trim();
}
export function useCurrencyFormatter() {
const currency = useCurrency();
const roundPrices = JSON.parse(useGetSetting("round_prices").data?.value ?? "false");
return new Intl.NumberFormat(undefined, {
style: "currency",
currency: currency,
currencyDisplay: "narrowSymbol",
notation: roundPrices ? "compact" : "standard",
});
}

577
pdm.lock generated
View File

@@ -5,7 +5,7 @@
groups = ["default", "dev"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:5c40f6631af830785a8f2852215526e71c6db0d73685f6cac6ad153afe3697f1"
content_hash = "sha256:4bd60f905c3a76c3818c8d812784be141f10cedb41534379bb2ea40723ee9469"
[[metadata.targets]]
requires_python = ">=3.9,<=3.12"
@@ -40,20 +40,18 @@ files = [
[[package]]
name = "alembic"
version = "1.14.0"
requires_python = ">=3.8"
version = "1.15.1"
requires_python = ">=3.9"
summary = "A database migration tool for SQLAlchemy."
groups = ["default"]
dependencies = [
"Mako",
"SQLAlchemy>=1.3.0",
"importlib-metadata; python_version < \"3.9\"",
"importlib-resources; python_version < \"3.9\"",
"typing-extensions>=4",
"SQLAlchemy>=1.4.0",
"typing-extensions>=4.12",
]
files = [
{file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25"},
{file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b"},
{file = "alembic-1.15.1-py3-none-any.whl", hash = "sha256:197de710da4b3e91cf66a826a5b31b5d59a127ab41bd0fc42863e2902ce2bbbe"},
{file = "alembic-1.15.1.tar.gz", hash = "sha256:e1a1c738577bca1f27e68728c910cd389b9a92152ff91d902da649c192e30c49"},
]
[[package]]
@@ -146,8 +144,8 @@ files = [
[[package]]
name = "black"
version = "24.4.2"
requires_python = ">=3.8"
version = "25.1.0"
requires_python = ">=3.9"
summary = "The uncompromising code formatter."
groups = ["dev"]
dependencies = [
@@ -160,24 +158,24 @@ dependencies = [
"typing-extensions>=4.0.1; python_version < \"3.11\"",
]
files = [
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
{file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"},
{file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"},
{file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"},
{file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"},
{file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"},
{file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"},
{file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"},
{file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"},
{file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"},
{file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"},
{file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"},
{file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"},
{file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"},
{file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"},
{file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"},
{file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"},
{file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"},
{file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"},
]
[[package]]
@@ -253,18 +251,18 @@ files = [
[[package]]
name = "fastapi"
version = "0.115.5"
version = "0.115.11"
requires_python = ">=3.8"
summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
groups = ["default"]
dependencies = [
"pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4",
"starlette<0.42.0,>=0.40.0",
"starlette<0.47.0,>=0.40.0",
"typing-extensions>=4.8.0",
]
files = [
{file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"},
{file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"},
{file = "fastapi-0.115.11-py3-none-any.whl", hash = "sha256:32e1541b7b74602e4ef4a0260ecaf3aadf9d4f19590bba3e1bf2ac4666aa2c64"},
{file = "fastapi-0.115.11.tar.gz", hash = "sha256:cc81f03f688678b92600a65a5e618b93592c65005db37157147204d8924bf94f"},
]
[[package]]
@@ -409,7 +407,7 @@ files = [
[[package]]
name = "httpx"
version = "0.28.0"
version = "0.28.1"
requires_python = ">=3.8"
summary = "The next generation HTTP client."
groups = ["default", "dev"]
@@ -420,8 +418,8 @@ dependencies = [
"idna",
]
files = [
{file = "httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc"},
{file = "httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0"},
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[[package]]
@@ -605,7 +603,7 @@ files = [
[[package]]
name = "pre-commit"
version = "3.7.1"
version = "4.1.0"
requires_python = ">=3.9"
summary = "A framework for managing and maintaining multi-language pre-commit hooks."
groups = ["dev"]
@@ -617,19 +615,19 @@ dependencies = [
"virtualenv>=20.10.0",
]
files = [
{file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"},
{file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"},
{file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"},
{file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"},
]
[[package]]
name = "prometheus-client"
version = "0.21.0"
version = "0.21.1"
requires_python = ">=3.8"
summary = "Python client for the Prometheus monitoring system."
groups = ["default"]
files = [
{file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"},
{file = "prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e"},
{file = "prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301"},
{file = "prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb"},
]
[[package]]
@@ -691,23 +689,23 @@ files = [
[[package]]
name = "pydantic"
version = "2.10.2"
version = "2.10.6"
requires_python = ">=3.8"
summary = "Data validation using Python type hints"
groups = ["default"]
dependencies = [
"annotated-types>=0.6.0",
"pydantic-core==2.27.1",
"pydantic-core==2.27.2",
"typing-extensions>=4.12.2",
]
files = [
{file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"},
{file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"},
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
]
[[package]]
name = "pydantic-core"
version = "2.27.1"
version = "2.27.2"
requires_python = ">=3.8"
summary = "Core functionality for Pydantic validation and serialization"
groups = ["default"]
@@ -715,79 +713,79 @@ dependencies = [
"typing-extensions!=4.7.0,>=4.6.0",
]
files = [
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
{file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
{file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
{file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
{file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
{file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
{file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
{file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
{file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
{file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
{file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
{file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"},
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"},
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"},
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"},
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"},
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"},
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"},
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"},
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"},
{file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"},
{file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"},
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"},
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"},
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"},
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"},
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"},
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"},
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"},
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"},
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"},
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"},
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"},
{file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"},
{file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"},
{file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"},
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"},
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"},
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"},
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"},
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"},
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"},
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"},
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"},
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"},
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"},
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"},
{file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"},
{file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"},
{file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"},
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"},
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"},
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"},
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"},
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"},
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"},
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"},
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"},
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"},
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"},
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"},
{file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"},
{file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"},
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"},
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"},
{file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"},
]
[[package]]
@@ -803,7 +801,7 @@ files = [
[[package]]
name = "pytest"
version = "8.2.2"
version = "8.3.5"
requires_python = ">=3.8"
summary = "pytest: simple powerful testing with Python"
groups = ["dev"]
@@ -812,26 +810,26 @@ dependencies = [
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
"iniconfig",
"packaging",
"pluggy<2.0,>=1.5",
"pluggy<2,>=1.5",
"tomli>=1; python_version < \"3.11\"",
]
files = [
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
]
[[package]]
name = "pytest-asyncio"
version = "0.23.8"
requires_python = ">=3.8"
version = "0.25.3"
requires_python = ">=3.9"
summary = "Pytest support for asyncio"
groups = ["dev"]
dependencies = [
"pytest<9,>=7.0.0",
"pytest<9,>=8.2",
]
files = [
{file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
{file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
{file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"},
{file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"},
]
[[package]]
@@ -882,33 +880,34 @@ files = [
[[package]]
name = "ruff"
version = "0.4.4"
version = "0.11.0"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["dev"]
files = [
{file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"},
{file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"},
{file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"},
{file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"},
{file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"},
{file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"},
{file = "ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb"},
{file = "ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639"},
{file = "ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88"},
{file = "ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2"},
{file = "ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8"},
{file = "ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905"},
{file = "ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329"},
{file = "ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844"},
{file = "ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e"},
{file = "ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db"},
{file = "ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445"},
{file = "ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7"},
{file = "ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7"},
{file = "ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6"},
{file = "ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2"},
{file = "ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21"},
{file = "ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657"},
{file = "ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2"},
]
[[package]]
name = "scheduler"
version = "0.8.7"
version = "0.8.8"
requires_python = ">=3.9"
summary = "A simple in-process python scheduler library with asyncio, threading and timezone support."
groups = ["default"]
@@ -916,19 +915,19 @@ dependencies = [
"typeguard>=3.0.0",
]
files = [
{file = "scheduler-0.8.7-py3-none-any.whl", hash = "sha256:0917fe659a4e62f3d306af7c36f77c5161ef73a7a645bf231db6bd19722b9a3e"},
{file = "scheduler-0.8.7.tar.gz", hash = "sha256:ab54d6474649650c5d040b0cc0ae314c636b110fd0dd83254a0481bc93415ee5"},
{file = "scheduler-0.8.8-py3-none-any.whl", hash = "sha256:8f52ea6390757e4f42a8becbcb474e7744a3082ea5e1cdb0c972ea8b2c5d1891"},
{file = "scheduler-0.8.8.tar.gz", hash = "sha256:4575a12cd269e4e4896409836fd911560cb63fb7634360ee62aa2fa4ec495ffd"},
]
[[package]]
name = "setuptools"
version = "70.0.0"
requires_python = ">=3.8"
version = "76.0.0"
requires_python = ">=3.9"
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
groups = ["default"]
files = [
{file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"},
{file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"},
{file = "setuptools-76.0.0-py3-none-any.whl", hash = "sha256:199466a166ff664970d0ee145839f5582cb9bca7a0a3a2e795b6a9cb2308e9c6"},
{file = "setuptools-76.0.0.tar.gz", hash = "sha256:43b4ee60e10b0d0ee98ad11918e114c70701bc6051662a9a675a0496c1a158f4"},
]
[[package]]
@@ -944,50 +943,50 @@ files = [
[[package]]
name = "sqlalchemy"
version = "2.0.36"
version = "2.0.39"
requires_python = ">=3.7"
summary = "Database Abstraction Library"
groups = ["default"]
dependencies = [
"greenlet!=0.4.17; (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.13\"",
"greenlet!=0.4.17; (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.14\"",
"importlib-metadata; python_version < \"3.8\"",
"typing-extensions>=4.6.0",
]
files = [
{file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"},
{file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"},
{file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"},
{file = "sqlalchemy-2.0.39-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6827f8c1b2f13f1420545bd6d5b3f9e0b85fe750388425be53d23c760dcf176b"},
{file = "sqlalchemy-2.0.39-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9f119e7736967c0ea03aff91ac7d04555ee038caf89bb855d93bbd04ae85b41"},
{file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4600c7a659d381146e1160235918826c50c80994e07c5b26946a3e7ec6c99249"},
{file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a06e6c8e31c98ddc770734c63903e39f1947c9e3e5e4bef515c5491b7737dde"},
{file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4c433f78c2908ae352848f56589c02b982d0e741b7905228fad628999799de4"},
{file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bd5c5ee1448b6408734eaa29c0d820d061ae18cb17232ce37848376dcfa3e92"},
{file = "sqlalchemy-2.0.39-cp310-cp310-win32.whl", hash = "sha256:87a1ce1f5e5dc4b6f4e0aac34e7bb535cb23bd4f5d9c799ed1633b65c2bcad8c"},
{file = "sqlalchemy-2.0.39-cp310-cp310-win_amd64.whl", hash = "sha256:871f55e478b5a648c08dd24af44345406d0e636ffe021d64c9b57a4a11518304"},
{file = "sqlalchemy-2.0.39-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a28f9c238f1e143ff42ab3ba27990dfb964e5d413c0eb001b88794c5c4a528a9"},
{file = "sqlalchemy-2.0.39-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08cf721bbd4391a0e765fe0fe8816e81d9f43cece54fdb5ac465c56efafecb3d"},
{file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a8517b6d4005facdbd7eb4e8cf54797dbca100a7df459fdaff4c5123265c1cd"},
{file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b2de1523d46e7016afc7e42db239bd41f2163316935de7c84d0e19af7e69538"},
{file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:412c6c126369ddae171c13987b38df5122cb92015cba6f9ee1193b867f3f1530"},
{file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b35e07f1d57b79b86a7de8ecdcefb78485dab9851b9638c2c793c50203b2ae8"},
{file = "sqlalchemy-2.0.39-cp311-cp311-win32.whl", hash = "sha256:3eb14ba1a9d07c88669b7faf8f589be67871d6409305e73e036321d89f1d904e"},
{file = "sqlalchemy-2.0.39-cp311-cp311-win_amd64.whl", hash = "sha256:78f1b79132a69fe8bd6b5d91ef433c8eb40688ba782b26f8c9f3d2d9ca23626f"},
{file = "sqlalchemy-2.0.39-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c457a38351fb6234781d054260c60e531047e4d07beca1889b558ff73dc2014b"},
{file = "sqlalchemy-2.0.39-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:018ee97c558b499b58935c5a152aeabf6d36b3d55d91656abeb6d93d663c0c4c"},
{file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a8120d6fc185f60e7254fc056a6742f1db68c0f849cfc9ab46163c21df47"},
{file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2cf5b5ddb69142511d5559c427ff00ec8c0919a1e6c09486e9c32636ea2b9dd"},
{file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f03143f8f851dd8de6b0c10784363712058f38209e926723c80654c1b40327a"},
{file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06205eb98cb3dd52133ca6818bf5542397f1dd1b69f7ea28aa84413897380b06"},
{file = "sqlalchemy-2.0.39-cp312-cp312-win32.whl", hash = "sha256:7f5243357e6da9a90c56282f64b50d29cba2ee1f745381174caacc50d501b109"},
{file = "sqlalchemy-2.0.39-cp312-cp312-win_amd64.whl", hash = "sha256:2ed107331d188a286611cea9022de0afc437dd2d3c168e368169f27aa0f61338"},
{file = "sqlalchemy-2.0.39-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2600a50d590c22d99c424c394236899ba72f849a02b10e65b4c70149606408b5"},
{file = "sqlalchemy-2.0.39-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4eff9c270afd23e2746e921e80182872058a7a592017b2713f33f96cc5f82e32"},
{file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7332868ce891eda48896131991f7f2be572d65b41a4050957242f8e935d5d7"},
{file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125a7763b263218a80759ad9ae2f3610aaf2c2fbbd78fff088d584edf81f3782"},
{file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:04545042969833cb92e13b0a3019549d284fd2423f318b6ba10e7aa687690a3c"},
{file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:805cb481474e111ee3687c9047c5f3286e62496f09c0e82e8853338aaaa348f8"},
{file = "sqlalchemy-2.0.39-cp39-cp39-win32.whl", hash = "sha256:34d5c49f18778a3665d707e6286545a30339ad545950773d43977e504815fa70"},
{file = "sqlalchemy-2.0.39-cp39-cp39-win_amd64.whl", hash = "sha256:35e72518615aa5384ef4fae828e3af1b43102458b74a8c481f69af8abf7e802a"},
{file = "sqlalchemy-2.0.39-py3-none-any.whl", hash = "sha256:a1c6b0a5e3e326a466d809b651c63f278b1256146a377a528b6938a279da334f"},
{file = "sqlalchemy-2.0.39.tar.gz", hash = "sha256:5d2d1fe548def3267b4c70a8568f108d1fed7cbbeccb9cc166e05af2abc25c22"},
]
[[package]]
@@ -1005,13 +1004,13 @@ files = [
[[package]]
name = "sqlalchemy"
version = "2.0.36"
version = "2.0.39"
extras = ["aiomysql", "aiosqlite", "asyncio", "postgresql_asyncpg"]
requires_python = ">=3.7"
summary = "Database Abstraction Library"
groups = ["default"]
dependencies = [
"SQLAlchemy==2.0.36",
"SQLAlchemy==2.0.39",
"aiomysql>=0.2.0",
"aiosqlite",
"asyncpg",
@@ -1022,40 +1021,40 @@ dependencies = [
"typing-extensions!=3.10.0.1",
]
files = [
{file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"},
{file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"},
{file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"},
{file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"},
{file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"},
{file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"},
{file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"},
{file = "sqlalchemy-2.0.39-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6827f8c1b2f13f1420545bd6d5b3f9e0b85fe750388425be53d23c760dcf176b"},
{file = "sqlalchemy-2.0.39-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9f119e7736967c0ea03aff91ac7d04555ee038caf89bb855d93bbd04ae85b41"},
{file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4600c7a659d381146e1160235918826c50c80994e07c5b26946a3e7ec6c99249"},
{file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a06e6c8e31c98ddc770734c63903e39f1947c9e3e5e4bef515c5491b7737dde"},
{file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4c433f78c2908ae352848f56589c02b982d0e741b7905228fad628999799de4"},
{file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bd5c5ee1448b6408734eaa29c0d820d061ae18cb17232ce37848376dcfa3e92"},
{file = "sqlalchemy-2.0.39-cp310-cp310-win32.whl", hash = "sha256:87a1ce1f5e5dc4b6f4e0aac34e7bb535cb23bd4f5d9c799ed1633b65c2bcad8c"},
{file = "sqlalchemy-2.0.39-cp310-cp310-win_amd64.whl", hash = "sha256:871f55e478b5a648c08dd24af44345406d0e636ffe021d64c9b57a4a11518304"},
{file = "sqlalchemy-2.0.39-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a28f9c238f1e143ff42ab3ba27990dfb964e5d413c0eb001b88794c5c4a528a9"},
{file = "sqlalchemy-2.0.39-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08cf721bbd4391a0e765fe0fe8816e81d9f43cece54fdb5ac465c56efafecb3d"},
{file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a8517b6d4005facdbd7eb4e8cf54797dbca100a7df459fdaff4c5123265c1cd"},
{file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b2de1523d46e7016afc7e42db239bd41f2163316935de7c84d0e19af7e69538"},
{file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:412c6c126369ddae171c13987b38df5122cb92015cba6f9ee1193b867f3f1530"},
{file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b35e07f1d57b79b86a7de8ecdcefb78485dab9851b9638c2c793c50203b2ae8"},
{file = "sqlalchemy-2.0.39-cp311-cp311-win32.whl", hash = "sha256:3eb14ba1a9d07c88669b7faf8f589be67871d6409305e73e036321d89f1d904e"},
{file = "sqlalchemy-2.0.39-cp311-cp311-win_amd64.whl", hash = "sha256:78f1b79132a69fe8bd6b5d91ef433c8eb40688ba782b26f8c9f3d2d9ca23626f"},
{file = "sqlalchemy-2.0.39-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c457a38351fb6234781d054260c60e531047e4d07beca1889b558ff73dc2014b"},
{file = "sqlalchemy-2.0.39-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:018ee97c558b499b58935c5a152aeabf6d36b3d55d91656abeb6d93d663c0c4c"},
{file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a8120d6fc185f60e7254fc056a6742f1db68c0f849cfc9ab46163c21df47"},
{file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2cf5b5ddb69142511d5559c427ff00ec8c0919a1e6c09486e9c32636ea2b9dd"},
{file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f03143f8f851dd8de6b0c10784363712058f38209e926723c80654c1b40327a"},
{file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06205eb98cb3dd52133ca6818bf5542397f1dd1b69f7ea28aa84413897380b06"},
{file = "sqlalchemy-2.0.39-cp312-cp312-win32.whl", hash = "sha256:7f5243357e6da9a90c56282f64b50d29cba2ee1f745381174caacc50d501b109"},
{file = "sqlalchemy-2.0.39-cp312-cp312-win_amd64.whl", hash = "sha256:2ed107331d188a286611cea9022de0afc437dd2d3c168e368169f27aa0f61338"},
{file = "sqlalchemy-2.0.39-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2600a50d590c22d99c424c394236899ba72f849a02b10e65b4c70149606408b5"},
{file = "sqlalchemy-2.0.39-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4eff9c270afd23e2746e921e80182872058a7a592017b2713f33f96cc5f82e32"},
{file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7332868ce891eda48896131991f7f2be572d65b41a4050957242f8e935d5d7"},
{file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125a7763b263218a80759ad9ae2f3610aaf2c2fbbd78fff088d584edf81f3782"},
{file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:04545042969833cb92e13b0a3019549d284fd2423f318b6ba10e7aa687690a3c"},
{file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:805cb481474e111ee3687c9047c5f3286e62496f09c0e82e8853338aaaa348f8"},
{file = "sqlalchemy-2.0.39-cp39-cp39-win32.whl", hash = "sha256:34d5c49f18778a3665d707e6286545a30339ad545950773d43977e504815fa70"},
{file = "sqlalchemy-2.0.39-cp39-cp39-win_amd64.whl", hash = "sha256:35e72518615aa5384ef4fae828e3af1b43102458b74a8c481f69af8abf7e802a"},
{file = "sqlalchemy-2.0.39-py3-none-any.whl", hash = "sha256:a1c6b0a5e3e326a466d809b651c63f278b1256146a377a528b6938a279da334f"},
{file = "sqlalchemy-2.0.39.tar.gz", hash = "sha256:5d2d1fe548def3267b4c70a8568f108d1fed7cbbeccb9cc166e05af2abc25c22"},
]
[[package]]
@@ -1133,8 +1132,8 @@ files = [
[[package]]
name = "uvicorn"
version = "0.32.1"
requires_python = ">=3.8"
version = "0.34.0"
requires_python = ">=3.9"
summary = "The lightning-fast ASGI server."
groups = ["default"]
dependencies = [
@@ -1143,8 +1142,8 @@ dependencies = [
"typing-extensions>=4.0; python_version < \"3.11\"",
]
files = [
{file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"},
{file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"},
{file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"},
{file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"},
]
[[package]]
@@ -1201,69 +1200,69 @@ files = [
[[package]]
name = "websockets"
version = "14.1"
version = "15.0.1"
requires_python = ">=3.9"
summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
groups = ["default"]
files = [
{file = "websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29"},
{file = "websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179"},
{file = "websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250"},
{file = "websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0"},
{file = "websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0"},
{file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"},
{file = "websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58"},
{file = "websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078"},
{file = "websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434"},
{file = "websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10"},
{file = "websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e"},
{file = "websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512"},
{file = "websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac"},
{file = "websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280"},
{file = "websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1"},
{file = "websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3"},
{file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6"},
{file = "websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0"},
{file = "websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89"},
{file = "websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23"},
{file = "websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e"},
{file = "websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09"},
{file = "websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed"},
{file = "websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d"},
{file = "websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707"},
{file = "websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a"},
{file = "websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45"},
{file = "websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58"},
{file = "websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058"},
{file = "websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4"},
{file = "websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05"},
{file = "websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0"},
{file = "websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f"},
{file = "websockets-14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a"},
{file = "websockets-14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6"},
{file = "websockets-14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56"},
{file = "websockets-14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c"},
{file = "websockets-14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b"},
{file = "websockets-14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78"},
{file = "websockets-14.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735"},
{file = "websockets-14.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a"},
{file = "websockets-14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc"},
{file = "websockets-14.1-cp39-cp39-win32.whl", hash = "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4"},
{file = "websockets-14.1-cp39-cp39-win_amd64.whl", hash = "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979"},
{file = "websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8"},
{file = "websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e"},
{file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098"},
{file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb"},
{file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7"},
{file = "websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d"},
{file = "websockets-14.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370"},
{file = "websockets-14.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a"},
{file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7"},
{file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0"},
{file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1"},
{file = "websockets-14.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5"},
{file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"},
{file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"},
{file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"},
{file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"},
{file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"},
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"},
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"},
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"},
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"},
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"},
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"},
{file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"},
{file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"},
{file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"},
{file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"},
{file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"},
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"},
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"},
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"},
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"},
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"},
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"},
{file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"},
{file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"},
{file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"},
{file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"},
{file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"},
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"},
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"},
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"},
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"},
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"},
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"},
{file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"},
{file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"},
{file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"},
{file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"},
{file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"},
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"},
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"},
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"},
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"},
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"},
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"},
{file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"},
{file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"},
{file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"},
{file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"},
]
[[package]]

View File

@@ -1,28 +1,28 @@
[project]
name = "spoolman"
version = "0.21.0"
version = "0.22.1"
description = "A web service that keeps track of 3D printing spools."
authors = [
{ name = "Donkie", email = "daniel.cf.hultgren@gmail.com" },
]
dependencies = [
"uvicorn~=0.32.1",
"uvicorn~=0.34",
"httptools>=0.6.4; platform_machine != \"armv7l\"",
"uvloop!=0.15.0,!=0.15.1,>=0.21.0; platform_machine != \"armv7l\" and sys_platform != \"win32\" and (sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\")",
"fastapi~=0.115.5",
"fastapi~=0.115",
"SQLAlchemy[aiomysql,aiosqlite,asyncio,postgresql_asyncpg]~=2.0",
"pydantic~=2.10.2",
"platformdirs~=4.3.6",
"alembic~=1.14.0",
"scheduler~=0.8.7",
"sqlalchemy-cockroachdb~=2.0.2",
"pydantic~=2.10",
"platformdirs~=4.3",
"alembic~=1.15",
"scheduler~=0.8",
"sqlalchemy-cockroachdb~=2.0",
"asyncpg~=0.30",
"psycopg2-binary~=2.9",
"setuptools~=70.0.0",
"WebSockets~=14.1",
"prometheus-client~=0.21.0",
"httpx~=0.28.0",
"hishel~=0.1.1",
"setuptools~=76.0",
"WebSockets~=15.0",
"prometheus-client~=0.21",
"httpx~=0.28",
"hishel~=0.1",
]
requires-python = ">=3.9,<=3.12"
@@ -31,12 +31,12 @@ text = "MIT"
[tool.pdm.dev-dependencies]
dev = [
"ruff==0.4.4",
"black~=24.4.2",
"pre-commit~=3.7.1",
"pytest~=8.2.1",
"pytest-asyncio~=0.23.7",
"httpx~=0.28.0",
"ruff==0.11.0",
"black~=25.1.0",
"pre-commit~=4.1.0",
"pytest~=8.3.5",
"pytest-asyncio~=0.25.3",
"httpx~=0.28.1",
]
[tool.pdm.scripts.docs]
@@ -60,7 +60,6 @@ select = [
"ALL",
]
ignore = [
"ANN101",
"A003",
"D101",
"D104",
@@ -85,6 +84,7 @@ ignore = [
[tool.ruff.lint.per-file-ignores]
"tests*/*" = [
"ANN201",
"ASYNC210",
"S101",
"PLR2004",
"D103",

View File

@@ -121,14 +121,13 @@ class FilamentParameters(BaseModel):
@field_validator("color_hex")
@classmethod
def color_hex_validator(cls, v: Optional[str]) -> Optional[str]: # noqa: ANN102
def color_hex_validator(cls, v: Optional[str]) -> Optional[str]:
"""Validate the color_hex field."""
if not v:
return None
clr = v.upper()
if clr.startswith("#"):
clr = clr[1:]
clr = clr.removeprefix("#")
for c in clr:
if c not in "0123456789ABCDEF":
@@ -141,14 +140,13 @@ class FilamentParameters(BaseModel):
@field_validator("multi_color_hexes")
@classmethod
def multi_color_hexes_validator(cls, v: Optional[str]) -> Optional[str]: # noqa: ANN102
def multi_color_hexes_validator(cls, v: Optional[str]) -> Optional[str]:
"""Validate the multi_color_hexes field."""
if not v:
return None
for clr_raw in v.split(","):
clr = clr_raw.upper()
if clr.startswith("#"):
clr = clr[1:]
clr = clr.removeprefix("#")
for c in clr:
if c not in "0123456789ABCDEF":
@@ -204,24 +202,24 @@ class FilamentUpdateParameters(FilamentParameters):
async def find(
*,
db: Annotated[AsyncSession, Depends(get_db_session)],
vendor_name_old: Optional[str] = Query(
alias="vendor_name",
default=None,
title="Vendor Name",
description="See vendor.name.",
deprecated=True,
),
vendor_id_old: Optional[str] = Query(
vendor_name_old: Annotated[
Optional[str],
Query(alias="vendor_name", title="Vendor Name", description="See vendor.name.", deprecated=True),
] = None,
vendor_id_old: Annotated[
Optional[str],
Query(
alias="vendor_id",
default=None,
title="Vendor ID",
description="See vendor.id.",
deprecated=True,
pattern=r"^-?\d+(,-?\d+)*$",
),
vendor_name: Optional[str] = Query(
] = None,
vendor_name: Annotated[
Optional[str],
Query(
alias="vendor.name",
default=None,
title="Vendor Name",
description=(
"Partial case-insensitive search term for the filament vendor name. "
@@ -229,9 +227,11 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
vendor_id: Optional[str] = Query(
] = None,
vendor_id: Annotated[
Optional[str],
Query(
alias="vendor.id",
default=None,
title="Vendor ID",
description=(
"Match an exact vendor ID. Separate multiple IDs with a comma. "
@@ -240,8 +240,10 @@ async def find(
pattern=r"^-?\d+(,-?\d+)*$",
examples=["1", "1,2"],
),
name: Optional[str] = Query(
default=None,
] = None,
name: Annotated[
Optional[str],
Query(
title="Filament Name",
description=(
"Partial case-insensitive search term for the filament name. Separate multiple terms with a comma. "
@@ -249,8 +251,10 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
material: Optional[str] = Query(
default=None,
] = None,
material: Annotated[
Optional[str],
Query(
title="Filament Material",
description=(
"Partial case-insensitive search term for the filament material. Separate multiple terms with a comma. "
@@ -258,8 +262,10 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
article_number: Optional[str] = Query(
default=None,
] = None,
article_number: Annotated[
Optional[str],
Query(
title="Filament Article Number",
description=(
"Partial case-insensitive search term for the filament article number. "
@@ -268,21 +274,27 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
color_hex: Optional[str] = Query(
default=None,
] = None,
color_hex: Annotated[
Optional[str],
Query(
title="Filament Color",
description="Match filament by similar color. Slow operation!",
),
color_similarity_threshold: float = Query(
default=20.0,
] = None,
color_similarity_threshold: Annotated[
float,
Query(
description=(
"The similarity threshold for color matching. "
"A value between 0.0-100.0, where 0 means match only exactly the same color."
),
example=20.0,
),
external_id: Optional[str] = Query(
default=None,
] = 20.0,
external_id: Annotated[
Optional[str],
Query(
description=(
"Find filaments imported by the given external ID. "
"Separate multiple IDs with a comma. "
@@ -291,24 +303,22 @@ async def find(
),
example="polymaker_pla_polysonicblack_1000_175",
),
sort: Optional[str] = Query(
default=None,
] = None,
sort: Annotated[
Optional[str],
Query(
title="Sort",
description=(
'Sort the results by the given field. Should be a comma-separate string with "field:direction" items.'
),
example="vendor.name:asc,spool_weight:desc",
),
limit: Optional[int] = Query(
default=None,
title="Limit",
description="Maximum number of items in the response.",
),
offset: int = Query(
default=0,
title="Offset",
description="Offset in the full result set if a limit is set.",
),
] = None,
limit: Annotated[
Optional[int],
Query(title="Limit", description="Maximum number of items in the response."),
] = None,
offset: Annotated[int, Query(title="Offset", description="Offset in the full result set if a limit is set.")] = 0,
) -> JSONResponse:
sort_by: dict[str, SortOrder] = {}
if sort is not None:

View File

@@ -128,46 +128,47 @@ class SpoolMeasureParameters(BaseModel):
async def find(
*,
db: Annotated[AsyncSession, Depends(get_db_session)],
filament_name_old: Optional[str] = Query(
alias="filament_name",
default=None,
title="Filament Name",
description="See filament.name.",
deprecated=True,
),
filament_id_old: Optional[str] = Query(
filament_name_old: Annotated[
Optional[str],
Query(alias="filament_name", title="Filament Name", description="See filament.name.", deprecated=True),
] = None,
filament_id_old: Annotated[
Optional[str],
Query(
alias="filament_id",
default=None,
title="Filament ID",
description="See filament.id.",
deprecated=True,
pattern=r"^-?\d+(,-?\d+)*$",
),
filament_material_old: Optional[str] = Query(
] = None,
filament_material_old: Annotated[
Optional[str],
Query(
alias="filament_material",
default=None,
title="Filament Material",
description="See filament.material.",
deprecated=True,
),
vendor_name_old: Optional[str] = Query(
alias="vendor_name",
default=None,
title="Vendor Name",
description="See filament.vendor.name.",
deprecated=True,
),
vendor_id_old: Optional[str] = Query(
] = None,
vendor_name_old: Annotated[
Optional[str],
Query(alias="vendor_name", title="Vendor Name", description="See filament.vendor.name.", deprecated=True),
] = None,
vendor_id_old: Annotated[
Optional[str],
Query(
alias="vendor_id",
default=None,
title="Vendor ID",
description="See filament.vendor.id.",
deprecated=True,
pattern=r"^-?\d+(,-?\d+)*$",
),
filament_name: Optional[str] = Query(
] = None,
filament_name: Annotated[
Optional[str],
Query(
alias="filament.name",
default=None,
title="Filament Name",
description=(
"Partial case-insensitive search term for the filament name. Separate multiple terms with a comma. "
@@ -175,17 +176,21 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
filament_id: Optional[str] = Query(
] = None,
filament_id: Annotated[
Optional[str],
Query(
alias="filament.id",
default=None,
title="Filament ID",
description="Match an exact filament ID. Separate multiple IDs with a comma.",
examples=["1", "1,2"],
pattern=r"^-?\d+(,-?\d+)*$",
),
filament_material: Optional[str] = Query(
] = None,
filament_material: Annotated[
Optional[str],
Query(
alias="filament.material",
default=None,
title="Filament Material",
description=(
"Partial case-insensitive search term for the filament material. Separate multiple terms with a comma. "
@@ -193,19 +198,24 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
filament_vendor_name: Optional[str] = Query(
] = None,
filament_vendor_name: Annotated[
Optional[str],
Query(
alias="filament.vendor.name",
default=None,
title="Vendor Name",
description=(
"Partial case-insensitive search term for the filament vendor name. Separate multiple terms with a comma. "
"Partial case-insensitive search term for the filament vendor name. "
"Separate multiple terms with a comma. "
"Specify an empty string to match spools with no vendor name. "
"Surround a term with quotes to search for the exact term."
),
),
filament_vendor_id: Optional[str] = Query(
] = None,
filament_vendor_id: Annotated[
Optional[str],
Query(
alias="filament.vendor.id",
default=None,
title="Vendor ID",
description=(
"Match an exact vendor ID. Separate multiple IDs with a comma. "
@@ -214,8 +224,10 @@ async def find(
examples=["1", "1,2"],
pattern=r"^-?\d+(,-?\d+)*$",
),
location: Optional[str] = Query(
default=None,
] = None,
location: Annotated[
Optional[str],
Query(
title="Location",
description=(
"Partial case-insensitive search term for the spool location. Separate multiple terms with a comma. "
@@ -223,8 +235,10 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
lot_nr: Optional[str] = Query(
default=None,
] = None,
lot_nr: Annotated[
Optional[str],
Query(
title="Lot/Batch Number",
description=(
"Partial case-insensitive search term for the spool lot number. Separate multiple terms with a comma. "
@@ -232,29 +246,26 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
allow_archived: bool = Query(
default=False,
title="Allow Archived",
description="Whether to include archived spools in the search results.",
),
sort: Optional[str] = Query(
default=None,
] = None,
allow_archived: Annotated[
bool,
Query(title="Allow Archived", description="Whether to include archived spools in the search results."),
] = False,
sort: Annotated[
Optional[str],
Query(
title="Sort",
description=(
'Sort the results by the given field. Should be a comma-separate string with "field:direction" items.'
),
example="filament.name:asc,filament.vendor.id:asc,location:desc",
),
limit: Optional[int] = Query(
default=None,
title="Limit",
description="Maximum number of items in the response.",
),
offset: int = Query(
default=0,
title="Offset",
description="Offset in the full result set if a limit is set.",
),
] = None,
limit: Annotated[
Optional[int],
Query(title="Limit", description="Maximum number of items in the response."),
] = None,
offset: Annotated[int, Query(title="Offset", description="Offset in the full result set if a limit is set.")] = 0,
) -> JSONResponse:
sort_by: dict[str, SortOrder] = {}
if sort is not None:

View File

@@ -80,16 +80,19 @@ class VendorUpdateParameters(VendorParameters):
)
async def find(
db: Annotated[AsyncSession, Depends(get_db_session)],
name: Optional[str] = Query(
default=None,
name: Annotated[
Optional[str],
Query(
title="Vendor Name",
description=(
"Partial case-insensitive search term for the vendor name. Separate multiple terms with a comma. "
"Surround a term with quotes to search for the exact term."
),
),
external_id: Optional[str] = Query(
default=None,
] = None,
external_id: Annotated[
Optional[str],
Query(
title="Vendor External ID",
description=(
"Exact match for the vendor external ID. "
@@ -98,24 +101,22 @@ async def find(
"Surround a term with quotes to search for the exact term."
),
),
sort: Optional[str] = Query(
default=None,
] = None,
sort: Annotated[
Optional[str],
Query(
title="Sort",
description=(
'Sort the results by the given field. Should be a comma-separate string with "field:direction" items.'
),
example="name:asc,id:desc",
),
limit: Optional[int] = Query(
default=None,
title="Limit",
description="Maximum number of items in the response.",
),
offset: int = Query(
default=0,
title="Offset",
description="Offset in the full result set if a limit is set.",
),
] = None,
limit: Annotated[
Optional[int],
Query(title="Limit", description="Maximum number of items in the response."),
] = None,
offset: Annotated[int, Query(title="Offset", description="Offset in the full result set if a limit is set.")] = 0,
) -> JSONResponse:
sort_by: dict[str, SortOrder] = {}
if sort is not None:

View File

@@ -59,11 +59,11 @@ class Database:
engine: Optional[AsyncEngine]
session_maker: Optional[async_sessionmaker[AsyncSession]]
def __init__(self: "Database", connection_url: URL) -> None:
def __init__(self, connection_url: URL) -> None:
"""Construct the Database wrapper and set config parameters."""
self.connection_url = connection_url
def is_file_based_sqlite(self: "Database") -> bool:
def is_file_based_sqlite(self) -> bool:
"""Return True if the database is file based."""
return (
self.connection_url.drivername[:6] == "sqlite"
@@ -71,7 +71,7 @@ class Database:
and self.connection_url.database != ":memory:"
)
def connect(self: "Database") -> None:
def connect(self) -> None:
"""Connect to the database."""
if env.get_logging_level() == logging.DEBUG:
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
@@ -79,15 +79,18 @@ class Database:
connect_args = {}
if self.connection_url.drivername == "sqlite+aiosqlite":
connect_args["timeout"] = 60
connection_options = {}
if self.connection_url.drivername == "mysql+aiomysql":
connection_options["pool_recycle"] = 3600
self.engine = create_async_engine(
self.connection_url,
connect_args=connect_args,
pool_pre_ping=True,
**connection_options,
)
self.session_maker = async_sessionmaker(self.engine, autocommit=False, autoflush=True, expire_on_commit=False)
def backup(self: "Database", target_path: Union[str, PathLike[str]]) -> None:
def backup(self, target_path: Union[str, PathLike[str]]) -> None:
"""Backup the database."""
if not self.is_file_based_sqlite() or self.connection_url.database is None:
return
@@ -108,7 +111,7 @@ class Database:
logger.info("Backup complete.")
def backup_and_rotate(
self: "Database",
self,
backup_folder: Union[str, PathLike[str]],
num_backups: int = 5,
) -> Optional[Path]:

View File

@@ -231,7 +231,8 @@ async def update(
elif isinstance(v, datetime):
setattr(spool, k, utc_timezone_naive(v))
elif k == "extra":
spool.extra = [models.SpoolField(key=k, value=v) for k, v in v.items()]
spool.extra = [f for f in spool.extra if f.key not in v]
spool.extra.extend([models.SpoolField(key=k, value=v) for k, v in v.items()])
else:
setattr(spool, k, v)
await db.commit()

View File

@@ -126,6 +126,6 @@ def add_where_clause_int_in(
value: Optional[Sequence[T]],
) -> Select:
"""Add a where clause to a select statement for a field."""
if value is not None and len(value) > 0:
if value is not None:
stmt = stmt.where(field.in_(value))
return stmt

View File

@@ -23,7 +23,7 @@ class DatabaseType(Enum):
SQLITE = "sqlite"
COCKROACHDB = "cockroachdb"
def to_drivername(self: "DatabaseType") -> str:
def to_drivername(self) -> str:
"""Get the drivername for the database type.
Returns:

View File

@@ -7,6 +7,7 @@ from collections.abc import Iterator
from enum import Enum
from pathlib import Path
from typing import Optional
from urllib.parse import urljoin
import hishel
from pydantic import BaseModel, Field, RootModel
@@ -121,7 +122,7 @@ class ExternalMaterialsFile(RootModel):
def get_external_db_url() -> str:
"""Get the external database URL from environment variables. Defaults to DEFAULT_EXTERNAL_DB_URL."""
return os.getenv("EXTERNAL_DB_URL", DEFAULT_EXTERNAL_DB_URL).strip("/") + "/"
return os.getenv("EXTERNAL_DB_URL", DEFAULT_EXTERNAL_DB_URL)
def get_external_db_sync_interval() -> int:
@@ -170,8 +171,8 @@ async def _sync() -> None:
url = get_external_db_url()
filaments = _parse_filaments_from_bytes(await _download_file(url + "filaments.json"))
materials = _parse_materials_from_bytes(await _download_file(url + "materials.json"))
filaments = _parse_filaments_from_bytes(await _download_file(urljoin(url, "filaments.json")))
materials = _parse_materials_from_bytes(await _download_file(urljoin(url, "materials.json")))
_write_to_local_cache("filaments.json", filaments.json().encode())
_write_to_local_cache("materials.json", materials.json().encode())

View File

@@ -31,15 +31,18 @@ root_logger.addHandler(console_handler)
# Fix uvicorn logging
logging.getLogger("uvicorn").setLevel(log_level)
logging.getLogger("uvicorn").removeHandler(logging.getLogger("uvicorn").handlers[0])
if logging.getLogger("uvicorn").handlers:
logging.getLogger("uvicorn").removeHandler(logging.getLogger("uvicorn").handlers[0])
logging.getLogger("uvicorn").addHandler(console_handler)
logging.getLogger("uvicorn.error").setLevel(log_level)
logging.getLogger("uvicorn.error").addHandler(console_handler)
logging.getLogger("uvicorn.access").setLevel(log_level)
logging.getLogger("uvicorn.access").removeHandler(logging.getLogger("uvicorn.access").handlers[0])
logging.getLogger("uvicorn.access").addHandler(console_handler)
access_handlers = logging.getLogger("uvicorn.access").handlers
if access_handlers:
logging.getLogger("uvicorn.access").setLevel(log_level)
logging.getLogger("uvicorn.access").removeHandler(access_handlers[0])
logging.getLogger("uvicorn.access").addHandler(console_handler)
# Get logger instance for this module
logger = logging.getLogger(__name__)
@@ -155,7 +158,7 @@ async def startup() -> None:
# There is some issue with the uvicorn worker that causes the process to hang when running alembic directly.
# See: https://github.com/sqlalchemy/alembic/discussions/1155
project_root = Path(__file__).parent.parent
subprocess.run(["alembic", "upgrade", "head"], check=True, cwd=project_root) # noqa: S603, S607, ASYNC101
subprocess.run(["alembic", "upgrade", "head"], check=True, cwd=project_root) # noqa: S603, S607, ASYNC221
# Setup scheduler
schedule = Scheduler()

View File

@@ -62,6 +62,7 @@ def parse_setting(key: str) -> SettingDefinition:
register_setting("currency", SettingType.STRING, json.dumps("EUR"))
register_setting("round_prices", SettingType.BOOLEAN, json.dumps(obj=False))
register_setting("print_presets", SettingType.ARRAY, json.dumps([]))
register_setting("extra_fields_vendor", SettingType.ARRAY, json.dumps([]))

View File

@@ -162,35 +162,35 @@ def random_empty_filament_empty_vendor_impl():
httpx.delete(f"{URL}/api/v1/filament/{filament['id']}").raise_for_status()
@pytest.fixture()
@pytest.fixture
def random_vendor():
"""Return a random vendor."""
with random_vendor_impl() as random_vendor:
yield random_vendor
@pytest.fixture()
@pytest.fixture
def random_empty_vendor():
"""Return a random vendor with only required fields specified."""
with random_empty_vendor_impl() as random_empty_vendor:
yield random_empty_vendor
@pytest.fixture()
@pytest.fixture
def random_filament():
"""Return a random filament."""
with random_filament_impl() as random_filament:
yield random_filament
@pytest.fixture()
@pytest.fixture
def random_empty_filament():
"""Return a random filament with only required fields specified."""
with random_empty_filament_impl() as random_empty_filament:
yield random_empty_filament
@pytest.fixture()
@pytest.fixture
def random_empty_filament_empty_vendor():
"""Return a random filament with only required fields specified and a vendor with only required fields specified."""
with random_empty_filament_empty_vendor_impl() as random_empty_filament_empty_vendor:

View File

@@ -138,12 +138,12 @@ def test_use_spool_not_found():
assert "123456789" in message
@pytest.mark.asyncio()
@pytest.mark.asyncio
async def test_use_spool_concurrent(random_filament: dict[str, Any]):
"""Test using a spool with many concurrent requests."""
# Setup
start_weight = 1000
result = httpx.post( # noqa: ASYNC100
result = httpx.post(
f"{URL}/api/v1/spool",
json={
"filament_id": random_filament["id"],
@@ -171,10 +171,10 @@ async def test_use_spool_concurrent(random_filament: dict[str, Any]):
)
# Verify
result = httpx.get(f"{URL}/api/v1/spool/{spool['id']}") # noqa: ASYNC100
result = httpx.get(f"{URL}/api/v1/spool/{spool['id']}")
result.raise_for_status()
spool = result.json()
assert spool["remaining_weight"] == pytest.approx(start_weight - (used_weight * requests))
# Clean up
httpx.delete(f"{URL}/api/v1/spool/{spool['id']}").raise_for_status() # noqa: ASYNC100
httpx.delete(f"{URL}/api/v1/spool/{spool['id']}").raise_for_status()