diff --git a/.github/workflows/branch-policy.yml b/.github/workflows/branch-policy.yml deleted file mode 100644 index 058eaaf..0000000 --- a/.github/workflows/branch-policy.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Branch Policy - -on: - pull_request: - branches: [main, beta, development] - -jobs: - check-source-branch: - name: Branch Policy Check - runs-on: ubuntu-latest - steps: - - name: Verify source branch - run: | - SOURCE="${{ github.head_ref }}" - TARGET="${{ github.base_ref }}" - - # hotfix/* is always allowed - if [[ "$SOURCE" == hotfix/* ]]; then - echo "✅ Hotfix branch '$SOURCE' is allowed to merge into '$TARGET'." - exit 0 - fi - - case "$TARGET" in - main) - if [ "$SOURCE" = "beta" ]; then - echo "✅ Branch '$SOURCE' is allowed to merge into main." - else - echo "❌ Branch '$SOURCE' is not allowed to merge into main." - echo "Only 'beta' and 'hotfix/*' branches can be merged into main." - exit 1 - fi - ;; - beta) - if [ "$SOURCE" = "development" ]; then - echo "✅ Branch '$SOURCE' is allowed to merge into beta." - else - echo "❌ Branch '$SOURCE' is not allowed to merge into beta." - echo "Only 'development' and 'hotfix/*' branches can be merged into beta." - exit 1 - fi - ;; - development) - if [[ "$SOURCE" == feature/* ]]; then - echo "✅ Branch '$SOURCE' is allowed to merge into development." - else - echo "❌ Branch '$SOURCE' is not allowed to merge into development." - echo "Only 'feature/*' and 'hotfix/*' branches can be merged into development." - exit 1 - fi - ;; - *) - echo "✅ No branch policy for target '$TARGET'." - ;; - esac diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml deleted file mode 100644 index 2c4a305..0000000 --- a/.github/workflows/code-quality.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Code Quality - -on: - pull_request: - branches: [main, master, development] - -concurrency: - group: quality-${{ github.head_ref || github.ref }} - cancel-in-progress: true - -jobs: - php-checks: - name: ${{ matrix.check.name }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - check: - - { name: "PHP Lint", command: "composer lint" } - - { name: "PHPCS", command: "./vendor/bin/phpcs --standard=phpcs.xml" } - - { name: "PHPMD", command: "./vendor/bin/phpmd lib text phpmd.xml" } - - { name: "Psalm", command: "./vendor/bin/psalm --threads=1 --no-cache --output-format=github" } - - { name: "PHPStan", command: "./vendor/bin/phpstan analyse --memory-limit=1G" } - - { name: "PHPUnit", command: "./vendor/bin/phpunit --colors=always" } - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, xml, ctype, iconv, intl, dom, filter, gd, json, posix, zip, soap - tools: composer:v2 - - - name: Cache Composer dependencies - uses: actions/cache@v4 - with: - path: vendor - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader - - - name: ${{ matrix.check.name }} - run: ${{ matrix.check.command }} - frontend-quality: - name: Frontend Quality - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: ESLint - run: npm run lint - - - name: Stylelint - run: npm run stylelint diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index 79f44b9..0000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Documentation - -on: - push: - branches: - - development - pull_request: - branches: - - development - -jobs: - deploy: - name: Deploy Documentation - runs-on: ubuntu-latest - # Only deploy on push, not on pull requests - if: github.event_name == 'push' - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js 18 - uses: actions/setup-node@v3 - with: - node-version: '18' - - - name: Clear build cache and install dependencies - timeout-minutes: 3 - run: | - cd docusaurus - rm -rf node_modules/.cache - rm -rf .docusaurus - rm -rf build - npm run ci - - - name: Verify build output - run: | - cd docusaurus/build - if [ ! -f index.html ]; then - echo "ERROR: index.html not found in build directory!" - exit 1 - fi - - - name: Create .nojekyll and CNAME files - run: | - cd docusaurus/build - touch .nojekyll - echo "procest.app" > CNAME - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docusaurus/build - publish_branch: gh-pages - user_name: 'github-actions[bot]' - user_email: 'github-actions[bot]@users.noreply.github.com' - force_orphan: false - allow_empty_commit: true - keep_files: false - - - name: Verify deployment - run: | - git fetch origin gh-pages - echo "Deployment completed. Latest commit: $(git rev-parse origin/gh-pages)" diff --git a/.gitignore b/.gitignore index 16ea727..0392705 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,11 @@ /docusaurus/node_modules/ /docusaurus/build/ /docusaurus/.docusaurus/ + +# Moved to apps-extra (parent folder) +/.claude/ +/.cursor/ +/.github/ +/.mcp.json +/.mcs.json +/openspec/schemas/ diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 30da9c4..08bfc9a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -7,6 +7,16 @@ - npm - A running Nextcloud instance with [OpenRegister](https://github.com/ConductionNL/openregister) installed +## OpenSpec (change management) + +This project uses OpenSpec for proposal → specs → design → tasks workflows. The schema is shared from `apps-extra/openspec/schemas`. **After cloning**, run the one-time setup: + +```powershell +.\openspec\setup-schemas.ps1 +``` + +See [openspec/README.md](openspec/README.md) for details. + ## Local Development This app is developed using the [nextcloud-docker-dev](https://github.com/juliushaertl/nextcloud-docker-dev) environment. The app is volume-mounted into the Nextcloud container. diff --git a/l10n/en.json b/l10n/en.json index 98c0ef9..1ddd561 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -8,6 +8,8 @@ "Task": "Task", "New case": "New case", "New task": "New task", + "New Case": "New Case", + "New Task": "New Task", "Title": "Title", "Description": "Description", "Status": "Status", @@ -49,8 +51,256 @@ "normal": "normal", "high": "high", "urgent": "urgent", + "Low": "Low", + "Normal": "Normal", + "High": "High", + "Urgent": "Urgent", "Back to list": "Back to list", "Previous": "Previous", - "Next": "Next" + "Next": "Next", + "Track and manage tasks": "Track and manage tasks", + "Documentation": "Documentation", + "Case Types": "Case Types", + "Configuration": "Configuration", + "Register and schema settings": "Register and schema settings", + "Refresh dashboard": "Refresh dashboard", + "Open Cases": "Open Cases", + "Overdue": "Overdue", + "Completed This Month": "Completed This Month", + "My Tasks": "My Tasks", + "Cases by Status": "Cases by Status", + "No open cases": "No open cases", + "My Work": "My Work", + "No items assigned to you": "No items assigned to you", + "View all my work": "View all my work", + "View all activity": "View all activity", + "Recent Activity": "Recent Activity", + "No recent activity": "No recent activity", + "Retry": "Retry", + "0 today": "0 today", + "+{n} today": "+{n} today", + "action needed": "action needed", + "all on track": "all on track", + "no data": "no data", + "{n} due today": "{n} due today", + "none due today": "none due today", + "avg {days} days": "avg {days} days", + "Failed to load dashboard data": "Failed to load dashboard data", + "Welcome to Procest! Get started by creating your first case type in Settings.": "Welcome to Procest! Get started by creating your first case type in Settings.", + "Welcome to Procest! Get started by creating your first case or task using the buttons above.": "Welcome to Procest! Get started by creating your first case or task using the buttons above.", + "Overdue Cases": "Overdue Cases", + "No overdue cases": "No overdue cases", + "View all overdue": "View all overdue", + "Manage cases and workflows": "Manage cases and workflows", + "Case Information": "Case Information", + "Case type": "Case type", + "Identifier": "Identifier", + "Handler": "Handler", + "Assign handler...": "Assign handler...", + "Start date": "Start date", + "Result": "Result", + "Result (required)": "Result (required)", + "Change status...": "Change status...", + "Change status": "Change status", + "Select result type...": "Select result type...", + "Confirm": "Confirm", + "Participants": "Participants", + "No participants assigned": "No participants assigned", + "Assign Handler": "Assign Handler", + "Reassign": "Reassign", + "Reassign handler to:": "Reassign handler to:", + "Unknown": "Unknown", + "Remove this participant?": "Remove this participant?", + "Create case": "Create case", + "Create task": "Create task", + "Select a case type...": "Select a case type...", + "Enter case title...": "Enter case title...", + "Enter task title...": "Enter task title...", + "Not set": "Not set", + "Initial status": "Initial status", + "Calculated deadline": "Calculated deadline", + "Case created with type '{type}'": "Case created with type '{type}'", + "Closed on {date}": "Closed on {date}", + "This will extend the deadline by {period}.": "This will extend the deadline by {period}.", + "Extend Deadline": "Extend Deadline", + "Reason": "Reason", + "Why is an extension needed?": "Why is an extension needed?", + "Extend deadline": "Extend deadline", + "Please select a result type": "Please select a result type", + "Result is required when closing a case": "Result is required when closing a case", + "Status changed from '{from}' to '{to}'": "Status changed from '{from}' to '{to}'", + "Status changed to '{status}'": "Status changed to '{status}'", + "Updated: {fields}": "Updated: {fields}", + "Are you sure you want to delete this case?": "Are you sure you want to delete this case?", + "This case has {count} linked tasks. Are you sure you want to delete it?": "This case has {count} linked tasks. Are you sure you want to delete it?", + "Deadline extended from {old} to {new}. Reason: {reason}": "Deadline extended from {old} to {new}. Reason: {reason}", + "No reason provided": "No reason provided", + "Configure case types": "Configure case types", + "Draft": "Draft", + "Published": "Published", + "Set as default": "Set as default", + "Only published case types can be set as default": "Only published case types can be set as default", + "Cannot delete: active cases are using this type": "Cannot delete: active cases are using this type", + "Failed to delete case type": "Failed to delete case type", + "{from} — (no end)": "{from} — (no end)", + "This will delete the case type and all {count} status types. Continue?": "This will delete the case type and all {count} status types. Continue?", + "Delete case type \"{title}\"?": "Delete case type \"{title}\"?", + "Failed to delete status type \"{name}\"": "Failed to delete status type \"{name}\"", + "New Case Type": "New Case Type", + "Case Type": "Case Type", + "Publish": "Publish", + "Unpublish": "Unpublish", + "Cannot publish:": "Cannot publish:", + "Saved successfully": "Saved successfully", + "General": "General", + "Statuses": "Statuses", + "Please fix the validation errors": "Please fix the validation errors", + "Failed to save case type": "Failed to save case type", + "Unpublishing this case type will prevent new cases from being created. Existing cases will continue to function. Continue?": "Unpublishing this case type will prevent new cases from being created. Existing cases will continue to function. Continue?", + "Save the case type first before adding status types.": "Save the case type first before adding status types.", + "Drag to reorder": "Drag to reorder", + "Final": "Final", + "Notify": "Notify", + "Name": "Name", + "Order": "Order", + "Final status": "Final status", + "Notify initiator": "Notify initiator", + "Notification text": "Notification text", + "No status types defined. Add at least one to publish this case type.": "No status types defined. Add at least one to publish this case type.", + "Add Status Type": "Add Status Type", + "Name *": "Name *", + "Order *": "Order *", + "Add": "Add", + "Status type name is required": "Status type name is required", + "Order is required": "Order is required", + "A status type with this order already exists": "A status type with this order already exists", + "Failed to add status type": "Failed to add status type", + "Failed to save": "Failed to save", + "At least one status type must be marked as final": "At least one status type must be marked as final", + "Delete status type \"{name}\"?": "Delete status type \"{name}\"?", + "Failed to delete status type": "Failed to delete status type", + "Due today": "Due today", + "Completed on {date}": "Completed on {date}", + "Case: {id}": "Case: {id}", + "Username": "Username", + "Title is required": "Title is required", + "Are you sure you want to delete this task?": "Are you sure you want to delete this task?", + "Optional description...": "Optional description...", + "Select priority": "Select priority", + "Select due date": "Select due date", + "Username (optional)": "Username (optional)", + "Link to a case (optional)": "Link to a case (optional)", + "Show completed": "Show completed", + "Cases and tasks assigned to you will appear here": "Cases and tasks assigned to you will appear here", + "All caught up!": "All caught up!", + "All your items are completed": "All your items are completed", + "Due this week": "Due this week", + "Upcoming": "Upcoming", + "No deadline": "No deadline", + "Completed": "Completed", + "All": "All", + "CASE": "CASE", + "TASK": "TASK", + "No result recorded yet": "No result recorded yet", + "Type: {type}": "Type: {type}", + "Deadline & Timing": "Deadline & Timing", + "Started": "Started", + "Deadline": "Deadline", + "Processing time": "Processing time", + "Days elapsed": "Days elapsed", + "Extension: allowed (+{period})": "Extension: allowed (+{period})", + "Extension: already extended": "Extension: already extended", + "Extension: not allowed": "Extension: not allowed", + "Request Extension": "Request Extension", + "Activity": "Activity", + "Add a note...": "Add a note...", + "Add note": "Add note", + "No activity yet": "No activity yet", + "{field} is required": "{field} is required", + "Case type is required": "Case type is required", + "Case type '{name}' is a draft and cannot be used to create cases": "Case type '{name}' is a draft and cannot be used to create cases", + "Case type is not yet valid (valid from {date})": "Case type is not yet valid (valid from {date})", + "Case type has expired (valid until {date})": "Case type has expired (valid until {date})", + "Must be a valid ISO 8601 duration (e.g., P56D for 56 days, P8W for 8 weeks, P2M for 2 months)": "Must be a valid ISO 8601 duration (e.g., P56D for 56 days, P8W for 8 weeks, P2M for 2 months)", + "Must be a valid ISO 8601 duration (e.g., P56D)": "Must be a valid ISO 8601 duration (e.g., P56D)", + "Must be a valid ISO 8601 duration (e.g., P42D)": "Must be a valid ISO 8601 duration (e.g., P42D)", + "Must be a valid ISO 8601 duration (e.g., P28D)": "Must be a valid ISO 8601 duration (e.g., P28D)", + "Extension period is required when extension is allowed": "Extension period is required when extension is allowed", + "'Valid until' must be after 'Valid from'": "'Valid until' must be after 'Valid from'", + "Missing required fields: {fields}": "Missing required fields: {fields}", + "'Valid from' date must be set": "'Valid from' date must be set", + "At least one status type must be defined": "At least one status type must be defined", + "1 year": "1 year", + "{n} years": "{n} years", + "1 month": "1 month", + "{n} months": "{n} months", + "1 week": "1 week", + "{n} weeks": "{n} weeks", + "1 day": "1 day", + "{n} days": "{n} days", + "1 day overdue": "1 day overdue", + "{days} days overdue": "{days} days overdue", + "Due tomorrow": "Due tomorrow", + "{days} days remaining": "{days} days remaining", + "just now": "just now", + "{min} min ago": "{min} min ago", + "{hours} hours ago": "{hours} hours ago", + "yesterday": "yesterday", + "{days} days ago": "{days} days ago", + "Available": "Available", + "Active": "Active", + "Terminated": "Terminated", + "Disabled": "Disabled", + "Start": "Start", + "Complete": "Complete", + "Terminate": "Terminate", + "Disable": "Disable", + "Internal": "Internal", + "External": "External", + "Public": "Public", + "Restricted": "Restricted", + "Case sensitive": "Case sensitive", + "Confidential": "Confidential", + "Highly confidential": "Highly confidential", + "Secret": "Secret", + "Top secret": "Top secret", + "Purpose": "Purpose", + "Trigger": "Trigger", + "Subject": "Subject", + "Processing deadline": "Processing deadline", + "Origin": "Origin", + "Confidentiality": "Confidentiality", + "Responsible unit": "Responsible unit", + "Extension period": "Extension period", + "Service target": "Service target", + "Valid until": "Valid until", + "Procest settings": "Procest settings", + "No settings available yet": "No settings available yet", + "User settings will appear here in a future update.": "User settings will appear here in a future update.", + "Add Participant": "Add Participant", + "Role type": "Role type", + "Select role type...": "Select role type...", + "Participant": "Participant", + "Select user...": "Select user...", + "Failed to add participant": "Failed to add participant", + "by {user}": "by {user}", + "{days} days": "{days} days", + "No tasks yet": "No tasks yet", + "Case Type Management": "Case Type Management", + "Manage case types and their configurations": "Manage case types and their configurations", + "Case type schema": "Case type schema", + "Status type schema": "Status type schema", + "Initiator action": "Initiator action", + "Handler action": "Handler action", + "e.g., P56D (56 days)": "e.g., P56D (56 days)", + "e.g., P42D (42 days)": "e.g., P42D (42 days)", + "e.g., P28D (28 days)": "e.g., P28D (28 days)", + "Extension allowed": "Extension allowed", + "Publication required": "Publication required", + "Publication text": "Publication text", + "Reference process": "Reference process", + "Keywords": "Keywords", + "Comma-separated keywords": "Comma-separated keywords", + "Valid from": "Valid from" } } diff --git a/l10n/nl.json b/l10n/nl.json index 1f349eb..dc72057 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -8,6 +8,8 @@ "Task": "Taak", "New case": "Nieuwe zaak", "New task": "Nieuwe taak", + "New Case": "Nieuwe zaak", + "New Task": "Nieuwe taak", "Title": "Titel", "Description": "Omschrijving", "Status": "Status", @@ -49,8 +51,256 @@ "normal": "normaal", "high": "hoog", "urgent": "urgent", + "Low": "Laag", + "Normal": "Normaal", + "High": "Hoog", + "Urgent": "Urgent", "Back to list": "Terug naar lijst", "Previous": "Vorige", - "Next": "Volgende" + "Next": "Volgende", + "Track and manage tasks": "Beheer en volg taken", + "Documentation": "Documentatie", + "Case Types": "Zaaktypen", + "Configuration": "Configuratie", + "Register and schema settings": "Register- en schema-instellingen", + "Refresh dashboard": "Dashboard vernieuwen", + "Open Cases": "Open zaken", + "Overdue": "Te laat", + "Completed This Month": "Afgerond deze maand", + "My Tasks": "Mijn taken", + "Cases by Status": "Zaken per status", + "No open cases": "Geen open zaken", + "My Work": "Mijn werk", + "No items assigned to you": "Geen items aan u toegewezen", + "View all my work": "Bekijk al mijn werk", + "View all activity": "Bekijk alle activiteit", + "Recent Activity": "Recente activiteit", + "No recent activity": "Geen recente activiteit", + "Retry": "Opnieuw proberen", + "0 today": "0 vandaag", + "+{n} today": "+{n} vandaag", + "action needed": "actie vereist", + "all on track": "alles op schema", + "no data": "geen gegevens", + "{n} due today": "{n} vandaag", + "none due today": "geen vandaag", + "avg {days} days": "gem. {days} dagen", + "Failed to load dashboard data": "Dashboardgegevens laden mislukt", + "Welcome to Procest! Get started by creating your first case type in Settings.": "Welkom bij Procest! Begin met het aanmaken van uw eerste zaaktype in Instellingen.", + "Welcome to Procest! Get started by creating your first case or task using the buttons above.": "Welkom bij Procest! Begin met het aanmaken van uw eerste zaak of taak via de knoppen hierboven.", + "Overdue Cases": "Te late zaken", + "No overdue cases": "Geen te late zaken", + "View all overdue": "Bekijk alle te late", + "Manage cases and workflows": "Beheer zaken en workflows", + "Case Information": "Zaakinformatie", + "Case type": "Zaaktype", + "Identifier": "Identificatie", + "Handler": "Behandelaar", + "Assign handler...": "Behandelaar toewijzen...", + "Start date": "Startdatum", + "Result": "Resultaat", + "Result (required)": "Resultaat (verplicht)", + "Change status...": "Status wijzigen...", + "Change status": "Status wijzigen", + "Select result type...": "Selecteer resultaattype...", + "Confirm": "Bevestigen", + "Participants": "Deelnemers", + "No participants assigned": "Geen deelnemers toegewezen", + "Assign Handler": "Behandelaar toewijzen", + "Reassign": "Opnieuw toewijzen", + "Reassign handler to:": "Behandelaar opnieuw toewijzen aan:", + "Unknown": "Onbekend", + "Remove this participant?": "Deze deelnemer verwijderen?", + "Create case": "Zaak aanmaken", + "Create task": "Taak aanmaken", + "Select a case type...": "Selecteer een zaaktype...", + "Enter case title...": "Voer zaaktitel in...", + "Enter task title...": "Voer taaktitel in...", + "Not set": "Niet ingesteld", + "Initial status": "Initiële status", + "Calculated deadline": "Berekende deadline", + "Case created with type '{type}'": "Zaak aangemaakt met type '{type}'", + "Closed on {date}": "Gesloten op {date}", + "This will extend the deadline by {period}.": "Dit verlengt de deadline met {period}.", + "Extend Deadline": "Deadline verlengen", + "Reason": "Reden", + "Why is an extension needed?": "Waarom is een verlenging nodig?", + "Extend deadline": "Deadline verlengen", + "Please select a result type": "Selecteer een resultaattype", + "Result is required when closing a case": "Resultaat is verplicht bij sluiten van een zaak", + "Status changed from '{from}' to '{to}'": "Status gewijzigd van '{from}' naar '{to}'", + "Status changed to '{status}'": "Status gewijzigd naar '{status}'", + "Updated: {fields}": "Bijgewerkt: {fields}", + "Are you sure you want to delete this case?": "Weet u zeker dat u deze zaak wilt verwijderen?", + "This case has {count} linked tasks. Are you sure you want to delete it?": "Deze zaak heeft {count} gekoppelde taken. Weet u zeker dat u deze wilt verwijderen?", + "Deadline extended from {old} to {new}. Reason: {reason}": "Deadline verlengd van {old} naar {new}. Reden: {reason}", + "No reason provided": "Geen reden opgegeven", + "Configure case types": "Zaaktypen configureren", + "Draft": "Concept", + "Published": "Gepubliceerd", + "Set as default": "Als standaard instellen", + "Only published case types can be set as default": "Alleen gepubliceerde zaaktypen kunnen als standaard worden ingesteld", + "Cannot delete: active cases are using this type": "Kan niet verwijderen: actieve zaken gebruiken dit type", + "Failed to delete case type": "Zaaktype verwijderen mislukt", + "{from} — (no end)": "{from} — (geen eind)", + "This will delete the case type and all {count} status types. Continue?": "Dit verwijdert het zaaktype en alle {count} statustypen. Doorgaan?", + "Delete case type \"{title}\"?": "Zaaktype \"{title}\" verwijderen?", + "Failed to delete status type \"{name}\"": "Statustype \"{name}\" verwijderen mislukt", + "New Case Type": "Nieuw zaaktype", + "Case Type": "Zaaktype", + "Publish": "Publiceren", + "Unpublish": "Depubliceren", + "Cannot publish:": "Kan niet publiceren:", + "Saved successfully": "Succesvol opgeslagen", + "General": "Algemeen", + "Statuses": "Statussen", + "Please fix the validation errors": "Los de validatiefouten op", + "Failed to save case type": "Zaaktype opslaan mislukt", + "Unpublishing this case type will prevent new cases from being created. Existing cases will continue to function. Continue?": "Depubliceren voorkomt dat nieuwe zaken worden aangemaakt. Bestaande zaken blijven functioneren. Doorgaan?", + "Save the case type first before adding status types.": "Sla het zaaktype eerst op voordat u statustypen toevoegt.", + "Drag to reorder": "Sleep om te herschikken", + "Final": "Eind", + "Notify": "Melden", + "Name": "Naam", + "Order": "Volgorde", + "Final status": "Eindstatus", + "Notify initiator": "Initiator melden", + "Notification text": "Meldingstekst", + "No status types defined. Add at least one to publish this case type.": "Geen statustypen gedefinieerd. Voeg er minimaal één toe om dit zaaktype te publiceren.", + "Add Status Type": "Statustype toevoegen", + "Name *": "Naam *", + "Order *": "Volgorde *", + "Add": "Toevoegen", + "Status type name is required": "Naam statustype is verplicht", + "Order is required": "Volgorde is verplicht", + "A status type with this order already exists": "Een statustype met deze volgorde bestaat al", + "Failed to add status type": "Statustype toevoegen mislukt", + "Failed to save": "Opslaan mislukt", + "At least one status type must be marked as final": "Minimaal één statustype moet als eind worden gemarkeerd", + "Delete status type \"{name}\"?": "Statustype \"{name}\" verwijderen?", + "Failed to delete status type": "Statustype verwijderen mislukt", + "Due today": "Vandaag", + "Completed on {date}": "Afgerond op {date}", + "Case: {id}": "Zaak: {id}", + "Username": "Gebruikersnaam", + "Title is required": "Titel is verplicht", + "Are you sure you want to delete this task?": "Weet u zeker dat u deze taak wilt verwijderen?", + "Optional description...": "Optionele omschrijving...", + "Select priority": "Selecteer prioriteit", + "Select due date": "Selecteer deadline", + "Username (optional)": "Gebruikersnaam (optioneel)", + "Link to a case (optional)": "Koppel aan zaak (optioneel)", + "Show completed": "Toon voltooide", + "Cases and tasks assigned to you will appear here": "Zaken en taken die aan u zijn toegewezen verschijnen hier", + "All caught up!": "Alles bijgewerkt!", + "All your items are completed": "Al uw items zijn voltooid", + "Due this week": "Deze week", + "Upcoming": "Binnenkort", + "No deadline": "Geen deadline", + "Completed": "Voltooid", + "All": "Alles", + "CASE": "ZAAK", + "TASK": "TAAK", + "No result recorded yet": "Nog geen resultaat vastgelegd", + "Type: {type}": "Type: {type}", + "Deadline & Timing": "Deadline en timing", + "Started": "Gestart", + "Deadline": "Deadline", + "Processing time": "Verwerkingstijd", + "Days elapsed": "Dagen verstreken", + "Extension: allowed (+{period})": "Verlenging: toegestaan (+{period})", + "Extension: already extended": "Verlenging: al verlengd", + "Extension: not allowed": "Verlenging: niet toegestaan", + "Request Extension": "Verlenging aanvragen", + "Activity": "Activiteit", + "Add a note...": "Voeg een notitie toe...", + "Add note": "Notitie toevoegen", + "No activity yet": "Nog geen activiteit", + "{field} is required": "{field} is verplicht", + "Case type is required": "Zaaktype is verplicht", + "Case type '{name}' is a draft and cannot be used to create cases": "Zaaktype '{name}' is een concept en kan niet worden gebruikt om zaken aan te maken", + "Case type is not yet valid (valid from {date})": "Zaaktype is nog niet geldig (geldig vanaf {date})", + "Case type has expired (valid until {date})": "Zaaktype is verlopen (geldig tot {date})", + "Must be a valid ISO 8601 duration (e.g., P56D for 56 days, P8W for 8 weeks, P2M for 2 months)": "Moet een geldige ISO 8601-duur zijn (bijv. P56D voor 56 dagen, P8W voor 8 weken, P2M voor 2 maanden)", + "Must be a valid ISO 8601 duration (e.g., P56D)": "Moet een geldige ISO 8601-duur zijn (bijv. P56D)", + "Must be a valid ISO 8601 duration (e.g., P42D)": "Moet een geldige ISO 8601-duur zijn (bijv. P42D)", + "Must be a valid ISO 8601 duration (e.g., P28D)": "Moet een geldige ISO 8601-duur zijn (bijv. P28D)", + "Extension period is required when extension is allowed": "Verlengingsperiode is verplicht wanneer verlenging is toegestaan", + "'Valid until' must be after 'Valid from'": "'Geldig tot' moet na 'Geldig vanaf' liggen", + "Missing required fields: {fields}": "Ontbrekende verplichte velden: {fields}", + "'Valid from' date must be set": "'Geldig vanaf'-datum moet worden ingesteld", + "At least one status type must be defined": "Minimaal één statustype moet worden gedefinieerd", + "1 year": "1 jaar", + "{n} years": "{n} jaar", + "1 month": "1 maand", + "{n} months": "{n} maanden", + "1 week": "1 week", + "{n} weeks": "{n} weken", + "1 day": "1 dag", + "{n} days": "{n} dagen", + "1 day overdue": "1 dag te laat", + "{days} days overdue": "{days} dagen te laat", + "Due tomorrow": "Morgen", + "{days} days remaining": "{days} dagen resterend", + "just now": "zojuist", + "{min} min ago": "{min} min geleden", + "{hours} hours ago": "{hours} uur geleden", + "yesterday": "gisteren", + "{days} days ago": "{days} dagen geleden", + "Available": "Beschikbaar", + "Active": "Actief", + "Terminated": "Beëindigd", + "Disabled": "Uitgeschakeld", + "Start": "Start", + "Complete": "Voltooien", + "Terminate": "Beëindigen", + "Disable": "Uitschakelen", + "Internal": "Intern", + "External": "Extern", + "Public": "Openbaar", + "Restricted": "Beperkt", + "Case sensitive": "Hoofdlettergevoelig", + "Confidential": "Vertrouwelijk", + "Highly confidential": "Zeer vertrouwelijk", + "Secret": "Geheim", + "Top secret": "Staatsgeheim", + "Purpose": "Doel", + "Trigger": "Trigger", + "Subject": "Onderwerp", + "Processing deadline": "Verwerkingsdeadline", + "Origin": "Herkomst", + "Confidentiality": "Vertrouwelijkheid", + "Responsible unit": "Verantwoordelijke eenheid", + "Extension period": "Verlengingsperiode", + "Service target": "Servicedoel", + "Valid until": "Geldig tot", + "Procest settings": "Procest-instellingen", + "No settings available yet": "Nog geen instellingen beschikbaar", + "User settings will appear here in a future update.": "Gebruikersinstellingen verschijnen hier in een toekomstige update.", + "Add Participant": "Deelnemer toevoegen", + "Role type": "Roltype", + "Select role type...": "Selecteer roltype...", + "Participant": "Deelnemer", + "Select user...": "Selecteer gebruiker...", + "Failed to add participant": "Deelnemer toevoegen mislukt", + "by {user}": "door {user}", + "{days} days": "{days} dagen", + "No tasks yet": "Nog geen taken", + "Case Type Management": "Zaaktypebeheer", + "Manage case types and their configurations": "Beheer zaaktypen en hun configuraties", + "Case type schema": "Zaaktype schema", + "Status type schema": "Statustype schema", + "Initiator action": "Initiatoractie", + "Handler action": "Behandelaarsactie", + "e.g., P56D (56 days)": "bijv. P56D (56 dagen)", + "e.g., P42D (42 days)": "bijv. P42D (42 dagen)", + "e.g., P28D (28 days)": "bijv. P28D (28 dagen)", + "Extension allowed": "Verlenging toegestaan", + "Publication required": "Publicatie vereist", + "Publication text": "Publicatietekst", + "Reference process": "Referentieproces", + "Keywords": "Trefwoorden", + "Comma-separated keywords": "Door komma's gescheiden trefwoorden", + "Valid from": "Geldig vanaf" } } diff --git a/openspec/README.md b/openspec/README.md new file mode 100644 index 0000000..3828089 --- /dev/null +++ b/openspec/README.md @@ -0,0 +1,78 @@ +# OpenSpec Setup for Procest + +This project uses [OpenSpec](https://github.com/Fission-AI/OpenSpec) for change management (proposal → specs → design → tasks → review). The workflow schema is **shared** across all Nextcloud apps in the `apps-extra` folder. + +## How the shared schema works + +The OpenSpec schema files live in **one place**: + +``` +apps-extra/openspec/schemas/conduction/ +├── schema.yaml +└── templates/ + ├── proposal.md, spec.md, design.md, review.md, tasks.md +``` + +Procest does **not** have its own copy. Instead, `procest/openspec/schemas` is a **junction** (Windows directory link) that points to the shared folder. Both paths access the **same files**: + +- `apps-extra/openspec/schemas/conduction/schema.yaml` +- `procest/openspec/schemas/conduction/schema.yaml` ← same file via junction + +Editing either path updates the same file. Changes apply to all apps in `apps-extra` that use this schema. + +## After cloning: one-time setup + +The `openspec/schemas` folder is in `.gitignore` (it's a link to shared files), so it is not committed. After cloning, you must create the junction. + +### Prerequisites + +- Procest must be inside the `nextcloud-docker-dev` workspace structure: + ``` + nextcloud-docker-dev/ + └── workspace/server/apps-extra/ + ├── openspec/schemas/ ← shared schema (source of truth) + └── procest/ ← this app + ``` + +### Windows (PowerShell) + +From the procest root: + +```powershell +.\openspec\setup-schemas.ps1 +``` + +Or manually: + +```powershell +cd openspec +cmd /c mklink /J schemas "..\..\openspec\schemas" +``` + +### Linux / macOS + +```bash +cd openspec +ln -s ../../openspec/schemas schemas +``` + +### Verify + +After setup, both paths should show the same content: + +```powershell +# Should show identical content +Get-Content openspec\schemas\conduction\schema.yaml | Select-Object -First 3 +Get-Content ..\..\openspec\schemas\conduction\schema.yaml | Select-Object -First 3 +``` + +## Using OpenSpec + +With the schema linked, use the slash commands in your AI assistant: + +- `/opsx:propose "add feature X"` — create a new change +- `/opsx:apply` — implement tasks +- `/opsx:verify` — run verification +- `/opsx:archive` — archive completed change + +See [OpenSpec docs](https://github.com/Fission-AI/OpenSpec) for the full workflow. diff --git a/openspec/changes/archive/2026-03-03-complete-l10n/.openspec.yaml b/openspec/changes/archive/2026-03-03-complete-l10n/.openspec.yaml new file mode 100644 index 0000000..ed9e94a --- /dev/null +++ b/openspec/changes/archive/2026-03-03-complete-l10n/.openspec.yaml @@ -0,0 +1,3 @@ +schema: conduction +created: 2026-03-03 +status: archived diff --git a/openspec/changes/archive/2026-03-03-complete-l10n/design.md b/openspec/changes/archive/2026-03-03-complete-l10n/design.md new file mode 100644 index 0000000..aa81768 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-complete-l10n/design.md @@ -0,0 +1,96 @@ +# Design: Complete l10n + +## Context + +Procest has `l10n/en.json` and `l10n/nl.json` with 55 keys each. The codebase uses ~175 unique keys via `t('procest', '...')` across 25+ files (views, utils, services). ~120 keys are missing. Missing keys cause fallback to the key string (often English), so Dutch users see mixed Dutch/English. + +**User-reported**: With Nextcloud set to Nederlands, Procest displays almost entirely in English. This is caused by the incomplete l10n list (fallback to key), not by locale-loading. Exploration report: `exploration.md`. + +## Goals / Non-Goals + +**Goals:** +- Add all missing keys to en.json and nl.json +- Ensure valid JSON and correct placeholder syntax +- No code changes — l10n files only + +**Non-Goals:** +- New languages +- Changing existing translations +- Refactoring t() calls + +## File Map + +### Modified Files + +| File | Changes | +|------|---------| +| `l10n/en.json` | Add ~120 missing keys with English text | +| `l10n/nl.json` | Add ~120 missing keys with Dutch translations | + +### Unchanged Files + +| File | Reason | +|------|--------| +| All `src/` files | No code changes (loadTranslations fix reverted — caused empty text) | +| `l10n/*.pot` (if any) | Not used for this change | + +## Design Decisions + +### DD-01: Source of Truth for Keys + +**Decision**: Extract keys from code via grep/search of `t('procest', '...')` patterns. + +**Rationale**: The code is the source of truth for which strings need translation. Adding keys not used in code would create orphan entries. + +### DD-02: Placeholder Syntax + +**Decision**: Preserve `{name}` placeholders exactly in both en and nl. Only translate the surrounding text. + +**Rationale**: Nextcloud's t() uses these for interpolation. Changing `{days}` to `{dagen}` would break the substitution. Example: `"{days} days overdue"` → nl: `"{days} dagen te laat"`. + +### DD-03: Translation Approach + +**Decision**: Add keys in batches by area (navigation, dashboard, cases, tasks, case types, validation, utilities). Use the exploration analysis as the reference list. + +**Rationale**: Organized batches reduce errors and make review easier. The exploration already produced a categorized list. + +## Key Categories (from exploration.md) + +1. **Navigation & layout** — Track and manage tasks, Documentation, Case Types, Configuration, Register and schema settings +2. **Dashboard** — New Case, New Task, Open Cases, Overdue, Completed This Month, My Tasks, Cases by Status, KPI strings (+{n} today, avg {days} days, etc.), welcome messages +3. **Overdue panel** — Overdue Cases, No overdue cases, View all overdue +4. **Cases** — Case Information, Identifier, Handler, Result, Participants, Add Participant, Assign Handler, Reassign, etc. +5. **Case detail & extension** — Closed on {date}, Extend Deadline, Status changed from/to, Updated: {fields}, delete confirmations +6. **Case types (admin)** — Draft, Published, Publish, Unpublish, Configure case types, Statuses, delete/confirm dialogs +7. **Status types tab** — Save the case type first..., Drag to reorder, Final status, Notify initiator, Add Status Type, validation errors +8. **Tasks** — Due today, Completed on {date}, Case: {id}, Title is required, Create task, etc. +9. **My Work** — Show completed, All caught up!, Due this week, Upcoming, No deadline, Completed, All, CASE, TASK +10. **Result & activity** — Result, Type: {type}, Deadline & Timing, Extension: allowed (+{period}), Activity, Add note +11. **Validation** — {field} is required, four ISO duration variants (P56D, P42D, P28D, generic), Valid from/until +12. **Duration & relative time** — 1 year, {n} years, 1 day, {n} days, just now, yesterday, {days} days ago, Due tomorrow, {days} days remaining +13. **Task lifecycle** — Available, Active, Terminated, Disabled, Start, Complete, Terminate, Disable +14. **Priority (capitalized)** — Urgent, High, Normal, Low +15. **Confidentiality & origin** — Internal, External, Public, Restricted, Case sensitive, Confidential, etc. +16. **Case type fields** — Purpose, Trigger, Subject, Processing deadline, Origin, Responsible unit, etc. +17. **User settings** — Procest settings, No settings available yet +18. **Add participant** — Role type, Select role type..., Participant, Select user..., Failed to add participant + +## Risks / Trade-offs + +- **[Risk] Translation quality** → Dutch translations may need native review. Mitigation: Use consistent terminology (e.g., "zaak" for case, "taak" for task) matching existing nl.json. +- **[Trade-off] Large file** → en.json and nl.json will grow from 55 to ~175 entries. Acceptable; standard for localized apps. + +## Exploration Notes + +- **OverduePanel**: Uses `Overdue Cases`, `No overdue cases`, `View all overdue` — added to categories. +- **ISO duration**: Four distinct keys (generic + P56D, P42D, P28D). Add all. +- **Priority**: Both lowercase (en.json) and capitalized (taskHelpers) keys exist; add capitalized Urgent, High, Normal, Low. +- **QuickStatusDropdown**: `Change status` (no ellipsis) vs `Change status...` elsewhere — different keys. + +## Post-Apply: Rebuild Required + +After updating l10n files, run `npm run build` in the procest app directory. Nextcloud Vue apps compile assets at build time; l10n changes do not take effect until the app is rebuilt. Users may also need to hard refresh (Ctrl+Shift+R) or clear browser cache. + +## Open Questions + +None — exploration complete; `exploration.md` is the reference list. diff --git a/openspec/changes/archive/2026-03-03-complete-l10n/exploration.md b/openspec/changes/archive/2026-03-03-complete-l10n/exploration.md new file mode 100644 index 0000000..4125fd5 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-complete-l10n/exploration.md @@ -0,0 +1,378 @@ +# Exploration: Complete l10n + +**Date**: 2026-03-03 +**Scope**: Extract all `t('procest', '...')` keys from codebase, compare with l10n files, produce definitive missing-key list. + +--- + +## User-Reported Symptom & Diagnosis + +**Symptom**: Nextcloud language is set to Nederlands, but the Procest app displays almost entirely in English. + +**Cause**: Incomplete l10n list — not a locale-loading bug. Nextcloud correctly loads `l10n/nl.json` when the user's language is Dutch. However, when `t('procest', 'key')` is called and the key is missing from `nl.json`, Nextcloud falls back to the key string itself (which is typically the English source text). With only ~55 keys in `nl.json` and ~120 keys used in code, most UI strings fall back to English. + +**Fix**: Add all missing keys to `nl.json` with Dutch translations. No code changes required. + +### If Dutch still doesn't show after applying l10n + +**Cause 1**: Nextcloud Vue apps require a rebuild after l10n changes; browser/Nextcloud may cache old assets. + +**Cause 2 (investigated, reverted)**: Tried importing `t`, `n`, `loadTranslations` from `@nextcloud/l10n` and calling `loadTranslations('procest', callback)` before mount. This caused **all text to display empty** — reverted. The root cause for Dutch not showing remains unclear; may require Nextcloud/server-side investigation (locale injection, app template). + +**Steps**: +1. Run `npm run build` in the procest app directory +3. Hard refresh the browser (Ctrl+Shift+R) or clear cache +4. Optionally: disable and re-enable the app (`occ app:disable procest` then `occ app:enable procest`) to clear Nextcloud app cache + +--- + +## Summary + +| Metric | Value | +|--------|-------| +| Keys in en.json | 55 | +| Unique keys used in code | ~175 | +| Missing keys | ~120 | +| Files with t() calls | 25+ (views, utils, services) | + +--- + +## Key Sources + +### Views (Vue components) +- `MainMenu.vue` — navigation +- `Dashboard.vue`, `KpiCards.vue`, `StatusChart.vue`, `MyWorkPreview.vue`, `ActivityFeed.vue`, `OverduePanel.vue` +- `CaseList.vue`, `CaseDetail.vue`, `CaseCreateDialog.vue` +- `TaskList.vue`, `TaskDetail.vue`, `TaskCreateDialog.vue` +- `MyWork.vue` +- `CaseTypeList.vue`, `CaseTypeDetail.vue`, `StatusesTab.vue` +- `Settings.vue`, `UserSettings.vue` +- Case components: `ResultSection`, `AddParticipantDialog`, `DeadlinePanel`, `ActivityTimeline`, `ParticipantsSection`, `QuickStatusDropdown` + +### Utils +- `dashboardHelpers.js` — formatRelativeTime, getMyWorkItems +- `caseHelpers.js` — getCaseOverdueText, formatDeadlineCountdown +- `caseValidation.js` — validateCaseCreate, validateCaseUpdate +- `caseTypeValidation.js` — validateCaseType, validateForPublish, getFieldLabel +- `durationHelpers.js` — formatDuration, getDurationError +- `taskHelpers.js` — PRIORITY_LABELS, getOverdueText +- `taskLifecycle.js` — STATUS_LABELS, ACTION_LABELS + +### Services +- `taskApi.js` — formatTaskForDisplay + +--- + +## Missing Keys (Categorized) + +### 1. Navigation & layout +- `Track and manage tasks` +- `Documentation` +- `Case Types` +- `Configuration` +- `Register and schema settings` + +### 2. Dashboard +- `New Case` +- `New Task` +- `Refresh dashboard` +- `Open Cases` +- `Overdue` +- `Completed This Month` +- `My Tasks` +- `Cases by Status` +- `No open cases` +- `My Work` +- `No items assigned to you` +- `View all my work` +- `View all activity` +- `Recent Activity` +- `No recent activity` +- `Retry` +- `0 today` +- `+{n} today` +- `action needed` +- `all on track` +- `no data` +- `{n} due today` +- `none due today` +- `avg {days} days` +- `Failed to load dashboard data` +- `Welcome to Procest! Get started by creating your first case type in Settings.` +- `Welcome to Procest! Get started by creating your first case or task using the buttons above.` + +### 3. Overdue panel +- `Overdue Cases` +- `No overdue cases` +- `View all overdue` + +### 4. Cases +- `Manage cases and workflows` +- `Case` +- `Case Information` +- `Case type` +- `Identifier` +- `Handler` +- `Assign handler...` +- `Start date` +- `Result` +- `Result (required)` +- `Change status...` +- `Select result type...` +- `Confirm` +- `Participants` +- `No participants assigned` +- `Assign Handler` +- `Reassign` +- `Reassign handler to:` +- `Unknown` +- `Remove this participant?` +- `New Case` +- `New Task` +- `Create case` +- `Create task` +- `Select a case type...` +- `Enter case title...` +- `Not set` +- `Initial status` +- `Calculated deadline` +- `Case created with type '{type}'` + +### 5. Case detail & extension +- `Closed on {date}` +- `This will extend the deadline by {period}.` +- `Extend Deadline` +- `Reason` +- `Why is an extension needed?` +- `Extend deadline` +- `Please select a result type` +- `Result is required when closing a case` +- `Status changed from '{from}' to '{to}'` +- `Status changed to '{status}'` +- `Updated: {fields}` +- `Are you sure you want to delete this case?` +- `This case has {count} linked tasks. Are you sure you want to delete it?` +- `Deadline extended from {old} to {new}. Reason: {reason}` +- `No reason provided` + +### 6. Case types (admin) +- `Configure case types` +- `Draft` +- `Published` +- `Set as default` +- `Delete` +- `Only published case types can be set as default` +- `Cannot delete: active cases are using this type` +- `Failed to delete case type` +- `{from} — (no end)` +- `This will delete the case type and all {count} status types. Continue?` +- `Delete case type "{title}"?` +- `Failed to delete status type "{name}"` +- `New Case Type` +- `Case Type` +- `Publish` +- `Unpublish` +- `Cannot publish:` +- `Saved successfully` +- `General` +- `Statuses` +- `Please fix the validation errors` +- `Failed to save case type` +- `Unpublishing this case type will prevent new cases from being created. Existing cases will continue to function. Continue?` + +### 7. Status types tab +- `Save the case type first before adding status types.` +- `Drag to reorder` +- `Final` +- `Notify` +- `Name` +- `Order` +- `Final status` +- `Notify initiator` +- `Notification text` +- `No status types defined. Add at least one to publish this case type.` +- `Add Status Type` +- `Name *` +- `Order *` +- `Add` +- `Status type name is required` +- `Order is required` +- `A status type with this order already exists` +- `Failed to add status type` +- `Failed to save` +- `At least one status type must be marked as final` +- `Delete status type "{name}"?` +- `Failed to delete status type` + +### 8. Tasks +- `Due today` +- `Back to list` +- `New task` +- `Task` +- `Completed on {date}` +- `Case: {id}` +- `Username` +- `Title is required` +- `Are you sure you want to delete this task?` +- `New Task` +- `Enter task title...` +- `Optional description...` +- `Select priority` +- `Select due date` +- `Username (optional)` +- `Link to a case (optional)` +- `Create task` +- `Change status` (no ellipsis — QuickStatusDropdown) + +### 9. My Work +- `Show completed` +- `Cases and tasks assigned to you will appear here` +- `All caught up!` +- `All your items are completed` +- `Due this week` +- `Upcoming` +- `No deadline` +- `Completed` +- `All` +- `Cases` +- `Tasks` +- `CASE` +- `TASK` + +### 10. Result & activity +- `Result` +- `No result recorded yet` +- `Type: {type}` +- `Deadline & Timing` +- `Started` +- `Deadline` +- `Processing time` +- `Days elapsed` +- `Extension: allowed (+{period})` +- `Extension: already extended` +- `Extension: not allowed` +- `Request Extension` +- `Activity` +- `Add a note...` +- `Add note` +- `No activity yet` + +### 11. Validation & utilities +- `{field} is required` +- `Title is required` +- `Case type is required` +- `Case type '{name}' is a draft and cannot be used to create cases` +- `Case type is not yet valid (valid from {date})` +- `Case type has expired (valid until {date})` +- `Must be a valid ISO 8601 duration (e.g., P56D for 56 days, P8W for 8 weeks, P2M for 2 months)` +- `Must be a valid ISO 8601 duration (e.g., P56D)` +- `Must be a valid ISO 8601 duration (e.g., P42D)` +- `Must be a valid ISO 8601 duration (e.g., P28D)` +- `Extension period is required when extension is allowed` +- `'Valid until' must be after 'Valid from'` +- `Missing required fields: {fields}` +- `'Valid from' date must be set` +- `At least one status type must be defined` +- `At least one status type must be marked as final` + +### 12. Duration & relative time +- `1 year` +- `{n} years` +- `1 month` +- `{n} months` +- `1 week` +- `{n} weeks` +- `1 day` +- `{n} days` +- `1 day overdue` +- `{days} days overdue` +- `Due today` +- `Due tomorrow` +- `{days} days remaining` +- `{days} days` +- `just now` +- `{min} min ago` +- `{hours} hours ago` +- `yesterday` +- `{days} days ago` + +### 13. Task lifecycle +- `Available` +- `Active` +- `Completed` +- `Terminated` +- `Disabled` +- `Start` +- `Complete` +- `Terminate` +- `Disable` + +### 14. Priority (capitalized) +- `Urgent` +- `High` +- `Normal` +- `Low` + +### 15. Confidentiality & origin +- `Internal` +- `External` +- `Public` +- `Restricted` +- `Case sensitive` +- `Confidential` +- `Highly confidential` +- `Secret` +- `Top secret` + +### 16. Case type fields (getFieldLabel) +- `Purpose` +- `Trigger` +- `Subject` +- `Processing deadline` +- `Origin` +- `Confidentiality` +- `Responsible unit` +- `Extension period` +- `Service target` +- `Valid until` + +### 17. User settings +- `Procest settings` +- `General` +- `No settings available yet` +- `User settings will appear here in a future update.` + +### 18. Add participant +- `Add Participant` +- `Role type` +- `Select role type...` +- `Participant` +- `Select user...` +- `Failed to add participant` + +--- + +## Placeholder Rules + +- **Preserve exactly**: `{field}`, `{count}`, `{days}`, `{n}`, `{name}`, `{title}`, `{from}`, `{to}`, `{period}`, `{date}`, `{user}`, `{type}`, `{old}`, `{new}`, `{reason}`, `{fields}`, `{min}`, `{hours}` +- **Escaped quotes**: Keys like `Status changed from '{from}' to '{to}'` keep the quotes in the key; the placeholders are interpolated. + +--- + +## Notes + +1. **Duplicate keys**: Some keys appear in both en.json and code with different casing (e.g. "New case" vs "New Case"). The code uses exact keys — use the key as it appears in the t() call. +2. **ISO duration**: Four distinct keys exist (generic + P56D, P42D, P28D variants). Add all four. +3. **Priority**: en.json has lowercase `low`, `normal`, `high`, `urgent`; taskHelpers uses `Urgent`, `High`, `Normal`, `Low`. Both sets are needed. +4. **OverduePanel**: Uses `Overdue Cases`, `No overdue cases`, `View all overdue` — not in original design. +5. **QuickStatusDropdown**: Uses `Change status` (no ellipsis) vs `Change status...` elsewhere. + +--- + +## Verification + +After adding keys: +1. `en.json` and `nl.json` must have identical key sets +2. No trailing commas in JSON +3. Placeholders unchanged in both en and nl +4. Spot-check: Dutch locale shows Dutch strings for dashboard, case detail, case type admin, task forms diff --git a/openspec/changes/archive/2026-03-03-complete-l10n/proposal.md b/openspec/changes/archive/2026-03-03-complete-l10n/proposal.md new file mode 100644 index 0000000..2716963 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-complete-l10n/proposal.md @@ -0,0 +1,55 @@ +# Proposal: Complete l10n + +## Summary + +Add all missing translation keys to the Procest app's l10n files (`l10n/en.json` and `l10n/nl.json`). Currently 55 keys exist; ~120 keys used in the codebase are missing (exploration: 25+ files across views, utils, services). Dutch users see untranslated English strings for many UI elements. This change completes the localization so the app displays correctly in both English and Dutch. + +## Problem + +The Procest app uses `t('procest', '...')` for user-facing strings throughout the codebase. When a key is missing from the active locale's JSON file, Nextcloud falls back to the key itself (often English). For Dutch users, this means many labels, messages, placeholders, and error texts appear in English instead of Dutch. + +**User-reported symptom**: When Nextcloud language is set to Nederlands, the Procest app still displays almost entirely in English. This is caused by the incomplete l10n list — not by a locale-loading bug. With ~55 keys in `nl.json` and ~120 keys used in code, most strings fall back to the (English) key. + +The admin-settings spec requires: "All labels, error messages, validation messages, and placeholder text MUST support English and Dutch localization." + +## Scope — MVP + +**In scope:** +- Add all missing translation keys to `l10n/en.json` (English as source) +- Add Dutch translations for all keys to `l10n/nl.json` +- Preserve placeholder syntax (`{field}`, `{count}`, `{days}`, etc.) in both files +- Cover: navigation, dashboard, cases, tasks, case types, validation, utilities, user settings + +**Out of scope:** +- Additional languages (beyond en/nl) +- Changing existing translations (only adding missing ones) +- Extracting new strings from code (only add keys already used via t()) + +## Approach + +1. Use exploration report (`exploration.md`) as the definitive list of missing keys — already extracted from 25+ files (views, utils, services) +2. Add missing keys to `en.json` in batches by category (see design Key Categories) +3. Add corresponding Dutch translations to `nl.json`, preserving placeholder syntax +4. Verify JSON syntax and that en.json and nl.json have identical key sets +5. **Rebuild**: Run `npm run build` — Nextcloud Vue apps must be rebuilt after l10n changes for translations to take effect + +## Capabilities + +### New Capabilities + +- **localization**: Complete English and Dutch translations for all user-facing strings in the Procest app. Ensures compliance with admin-settings REQ (localization) and improves UX for Dutch users. + +## Impact + +- **Files**: `l10n/en.json`, `l10n/nl.json` +- **Backend**: None +- **Dependencies**: Nextcloud l10n system, existing t() calls in code + +## Dependencies + +- admin-settings spec (localization requirement) +- Existing codebase (no code changes — only l10n file updates) + +## Post-Completion Note + +Manual Dutch verification failed — Dutch texts are not displayed when Nextcloud is set to Nederlands. L10n files are complete; a follow-up change will address Dutch display (locale/translation loading). This change is ready to archive. diff --git a/openspec/changes/archive/2026-03-03-complete-l10n/tasks.md b/openspec/changes/archive/2026-03-03-complete-l10n/tasks.md new file mode 100644 index 0000000..3c64ac8 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-complete-l10n/tasks.md @@ -0,0 +1,57 @@ +# Tasks: Complete l10n + +**Reference**: Use `exploration.md` as the definitive list of ~120 missing keys. Keys must match exact form used in code. + +## 1. Add Missing Keys to en.json (by category) + +- [x] 1.1 Navigation & layout (Track and manage tasks, Documentation, Case Types, Configuration, Register and schema settings) +- [x] 1.2 Dashboard (New Case, New Task, Open Cases, Overdue, KPI strings, welcome messages, Retry, Failed to load dashboard data) +- [x] 1.3 Overdue panel (Overdue Cases, No overdue cases, View all overdue) +- [x] 1.4 Cases (Manage cases and workflows, Case Information, Identifier, Handler, Participants, Add Participant, Assign Handler, Reassign, etc.) +- [x] 1.5 Case detail & extension (Closed on {date}, Extend Deadline, Status changed from/to, Updated: {fields}, delete confirmations) +- [x] 1.6 Case types admin (Draft, Published, Publish, Unpublish, Configure case types, Statuses, delete/confirm dialogs) +- [x] 1.7 Status types tab (Save the case type first..., Drag to reorder, Final status, Notify initiator, Add Status Type, validation errors) +- [x] 1.8 Tasks (Due today, Completed on {date}, Case: {id}, Title is required, Create task, Change status, etc.) +- [x] 1.9 My Work (Show completed, All caught up!, Due this week, Upcoming, No deadline, Completed, All, CASE, TASK) +- [x] 1.10 Result & activity (Result, Type: {type}, Deadline & Timing, Extension strings, Activity, Add note) +- [x] 1.11 Validation (four ISO duration variants, {field} is required, Valid from/until, case type validation messages) +- [x] 1.12 Duration & relative time (1 year, {n} years, 1 day, {n} days, just now, yesterday, {days} days ago, Due tomorrow, {days} days remaining) +- [x] 1.13 Task lifecycle (Available, Active, Terminated, Disabled, Start, Complete, Terminate, Disable) +- [x] 1.14 Priority capitalized (Urgent, High, Normal, Low) +- [x] 1.15 Confidentiality & case type fields (Internal, External, Purpose, Trigger, Subject, etc.) +- [x] 1.16 User settings & Add participant (Procest settings, No settings available yet, Role type, Select role type..., etc.) + +## 2. Add Dutch Translations to nl.json + +- [x] 2.1 Add nl translations for all keys added in step 1 — match existing nl.json style (zaak, taak, etc.) +- [x] 2.2 Preserve placeholder syntax ({field}, {count}, {days}, {n}, {name}, {title}, {from}, {to}, {period}, {date}, {user}, {type}, {old}, {new}, {reason}, {fields}, {min}, {hours}) in Dutch strings + +## 3. Translation Loading Fix (reverted — caused all text to show empty) + +- [x] 3.0 ~~Import `t`, `n`, `loadTranslations` from `@nextcloud/l10n`~~ — REVERTED: loadTranslations approach caused all text to display empty; restored original global t/n + +## 4. Rebuild (required for l10n to take effect) + +- [x] 4.1 Run `npm run build` in the procest app directory — Nextcloud Vue apps must be rebuilt after l10n changes (on Windows, if `NODE_ENV=production` fails, use `npx webpack --config webpack.config.js --progress` or run from WSL) +- [x] 4.2 Hard refresh browser (Ctrl+Shift+R) or clear cache after rebuild +- [x] 4.3 Optionally: `occ app:disable procest` then `occ app:enable procest` to clear Nextcloud app cache + +## 5. Verify + +- [x] 5.1 Validate JSON syntax (no trailing commas, valid escaping) +- [x] 5.2 Verify key count: en.json and nl.json have identical keys (302 total) +- [x] 5.3 Spot-check: switch Nextcloud to Dutch — **FAILED**: Dutch texts are not shown (app displays English regardless of locale) + +## Verification Tasks + +- [x] V-auto Automated: `node openspec/verify-l10n.js` — JSON valid, key sync, placeholders, code coverage +- [x] V01 Manual test: Set Nextcloud language to Dutch, verify dashboard labels in Dutch — **FAILED**: Dutch not displayed +- [x] V02 Manual test: Verify case detail, case type admin, and task forms show Dutch labels — **FAILED**: Dutch not displayed +- [x] V03 Manual test: Verify error messages and validation text appear in Dutch — **FAILED**: Dutch not displayed +- [x] V04 Manual test: Verify Overdue panel, My Work, and relative time strings in Dutch — **FAILED**: Dutch not displayed + +--- + +## Archive Note + +**Status**: Archived. L10n keys added (302 total); automated verification passed. Manual Dutch verification failed — Dutch texts are not displayed in the app. A **follow-up change** will be created to fix Dutch display (locale/translation loading on the server or app side). This change completes the l10n file updates only. diff --git a/openspec/changes/archive/2026-03-03-complete-l10n/verify-report.md b/openspec/changes/archive/2026-03-03-complete-l10n/verify-report.md new file mode 100644 index 0000000..ef09997 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-complete-l10n/verify-report.md @@ -0,0 +1,59 @@ +# Verification Report: Complete l10n + +**Date**: 2026-03-03 +**Change**: complete-l10n (archived) +**Status**: PARTIAL — automated passed, manual Dutch verification failed + +--- + +## Automated Checks + +| Check | Result | +|-------|--------| +| en.json valid JSON | OK | +| nl.json valid JSON | OK | +| en.json and nl.json identical keys | OK (302 total) | +| Placeholders preserved in nl.json | OK (36 keys with placeholders) | +| All keys used in code exist in l10n | OK (275 keys) | + +--- + +## Fixes Applied During Verify + +18 keys were found in code but missing from l10n. Added to both en.json and nl.json: + +- `{days} days` +- `No tasks yet` +- `Case Type Management` +- `Manage case types and their configurations` +- `Case type schema` +- `Status type schema` +- `Initiator action` +- `Handler action` +- `e.g., P56D (56 days)` +- `e.g., P42D (42 days)` +- `e.g., P28D (28 days)` +- `Extension allowed` +- `Publication required` +- `Publication text` +- `Reference process` +- `Keywords` +- `Comma-separated keywords` +- `Valid from` + +--- + +## Manual Verification (Failed) + +Dutch texts are not displayed in the app when Nextcloud language is set to Nederlands. All manual verification tasks failed. A follow-up change will be created to fix Dutch display. + +- [x] V01: Set Nextcloud language to Dutch, verify dashboard labels in Dutch — **FAILED** +- [x] V02: Verify case detail, case type admin, and task forms show Dutch labels — **FAILED** +- [x] V03: Verify error messages and validation text appear in Dutch — **FAILED** +- [x] V04: Verify Overdue panel, My Work, and relative time strings in Dutch — **FAILED** + +--- + +## Verification Script + +Run `node openspec/verify-l10n.js` from the procest app root to re-run automated checks. diff --git a/openspec/changes/archive/2026-03-03-fix-dutch-display/.openspec.yaml b/openspec/changes/archive/2026-03-03-fix-dutch-display/.openspec.yaml new file mode 100644 index 0000000..ed9e94a --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-dutch-display/.openspec.yaml @@ -0,0 +1,3 @@ +schema: conduction +created: 2026-03-03 +status: archived diff --git a/openspec/changes/archive/2026-03-03-fix-dutch-display/design.md b/openspec/changes/archive/2026-03-03-fix-dutch-display/design.md new file mode 100644 index 0000000..f6bf282 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-dutch-display/design.md @@ -0,0 +1,125 @@ +# Design: Fix Displaying Dutch Language + +## Context + +The Procest app uses `Vue.mixin({ methods: { t, n } })` so `t` and `n` are available in all components. These are global functions — not imported from `@nextcloud/l10n`. The `@nextcloud/l10n` library uses `window._oc_l10n_registry_translations[appId]` for translations and `document.documentElement.dataset.locale` for locale. + +**Current flow**: `t('procest', key)` → `getAppTranslations('procest')` → `window._oc_l10n_registry_translations['procest']` → lookup or fallback to key. + +**Problem**: Dutch translations exist in `l10n/nl.json` (302 keys) but are not displayed. Either (1) registry is empty for app; (2) registry has wrong locale; (3) locale is always 'en'. + +**Failed attempt**: `loadTranslations('procest', callback)` before mount caused all text empty — suggests registration timing or wrong bundle format. + +## Goals / Non-Goals + +**Goals:** +- Dutch texts display when Nextcloud language is Nederlands +- No regression: English still works when locale is en +- Minimal changes to app code + +**Non-Goals:** +- Changing l10n file content (complete-l10n already done) +- New languages + +## File Map + +### Files to Investigate / Modify + +| File | Role | +|------|------| +| `src/main.js` | Entry point; may need to load/register translations before Vue mount | +| `src/settings.js` | Admin settings entry; same consideration | +| `templates/index.php` | If exists: app template; Nextcloud may inject l10n here | +| `appinfo/info.xml` | App metadata; no l10n config expected | + +### Unchanged Files + +| File | Reason | +|------|--------| +| `l10n/en.json`, `l10n/nl.json` | Complete; no changes | +| All Vue components | Use t() correctly; no changes | + +## Root Cause Hypotheses + +### H1: Server does not inject app translations + +Nextcloud may inject core translations but not app-specific ones. For app pages (TemplateResponse), the app may need to load its own l10n via `loadTranslations` or the template must include a script that registers them. + +**Check**: Inspect the rendered HTML for the Procest page; look for `window._oc_l10n_registry_translations` or `OC.L10n` scripts. Compare with a working Nextcloud app (e.g. Files, Calendar). + +### H2: App template missing or does not load l10n + +Procest uses `TemplateResponse(Application::APP_ID, 'index')`. If template does not exist, Nextcloud may use a default that does not load app l10n. If template exists, it may need to explicitly load `l10n/{locale}.json`. + +**Check**: Does `templates/index.php` exist? If not, Nextcloud falls back to a default — what does it include? + +### H3: loadTranslations approach — wrong usage or timing + +The previous attempt used `loadTranslations('procest', callback)` before mount. It caused empty text. Possible causes: +- Callback ran before Vue mount but `t` was already bound to a stale/empty registry +- `register()` was called with wrong format (e.g. `bundle.translations` vs `bundle`) +- `t` from global scope vs `t` from @nextcloud/l10n — different implementations? + +**Check**: `loadTranslations` fetches `l10n/{locale}.json` and calls `register(appName, result.translations)`. The nl.json format is `{ "translations": { "key": "value" } }`. So `result.translations` is correct. Verify `getLocale()` returns 'nl' when Nextcloud is Dutch. If locale is 'en', `loadTranslations` resolves immediately without fetching — that's correct. + +**Fix attempt**: Import `t`, `n` from `@nextcloud/l10n` and use them in the mixin (not globals). Ensure `loadTranslations` completes and registers before any component renders. The empty-text bug may have been caused by using global `t` before registration — the global might come from a different source (e.g. OC.L10n) that expects different registration. + +### H4: Locale not set on document + +`getLocale()` returns `document.documentElement.dataset.locale || 'en'`. If Nextcloud does not set `data-locale` on the HTML element for app pages, locale would always be 'en'. + +**Check**: In browser DevTools, when Nextcloud is Dutch: `document.documentElement.dataset.locale` and `document.documentElement.lang`. + +## Design Decisions + +### DD-01: Prefer app-side fix over server-side + +**Decision**: First try fixing in the app (main.js, settings.js) by loading and registering translations before mount. Only if that fails, investigate server-side (app template, Nextcloud core). + +**Rationale**: App-side changes are simpler to ship and don't require Nextcloud core changes. Many Nextcloud apps use `loadTranslations` successfully. + +### DD-02: Import t, n from @nextcloud/l10n + +**Decision**: Use `import { t, n, loadTranslations } from '@nextcloud/l10n'` and pass these to `Vue.mixin`. Do not rely on global `t`/`n`. + +**Rationale**: The global `t`/`n` may come from Nextcloud core or another source. Using the package ensures we use the same implementation that `loadTranslations` registers with. This may fix the empty-text bug (mismatch between global and @nextcloud/l10n registry). + +### DD-03: Async load before mount + +**Decision**: Call `loadTranslations('procest', () => { /* mount Vue */ })` and only mount Vue in the callback after translations are loaded. + +**Rationale**: Ensures translations are in `window._oc_l10n_registry_translations['procest']` before any component calls `t()`. For locale 'en', `loadTranslations` resolves immediately (no fetch). For 'nl', it fetches and registers. + +## Implementation Sketch + +```javascript +// main.js +import Vue from 'vue' +import { PiniaVuePlugin } from 'pinia' +import { translate as t, translatePlural as n, loadTranslations } from '@nextcloud/l10n' +import pinia from './pinia.js' +import router from './router/index.js' +import App from './App.vue' +import '@conduction/nextcloud-vue/css/index.css' + +Vue.mixin({ methods: { t, n } }) + +loadTranslations('procest', () => { + new Vue({ + pinia, + router, + render: h => h(App), + }).$mount('#content') +}) +``` + +## Risks / Trade-offs + +- **[Risk] loadTranslations empty-text bug recurs** → If the fix was using global vs imported t, this should work. If not, need to debug: log `getLocale()`, `hasAppTranslations('procest')`, and registry contents before mount. +- **[Trade-off] Slight delay before mount** → For non-en locales, one extra network request. Acceptable; translations are small. + +## Open Questions + +1. Does Procest have a `templates/index.php`? If not, what template does Nextcloud use? +2. What does `document.documentElement.dataset.locale` show when Nextcloud is Dutch? +3. If loadTranslations + import t/n still fails, what does a working Nextcloud app (e.g. deck, files) do? diff --git a/openspec/changes/archive/2026-03-03-fix-dutch-display/exploration.md b/openspec/changes/archive/2026-03-03-fix-dutch-display/exploration.md new file mode 100644 index 0000000..1c18d86 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-dutch-display/exploration.md @@ -0,0 +1,119 @@ +# Exploration: Fix Displaying Dutch Language + +**Date**: 2026-03-03 +**Scope**: Identify why Dutch translations are not displayed when Nextcloud language is Nederlands; fix translation loading. + +--- + +## Context from complete-l10n (archived) + +- **L10n files**: `l10n/en.json` and `l10n/nl.json` have 302 keys each; Dutch translations are complete +- **Symptom**: User sets Nextcloud to Nederlands; Procest app still shows English +- **Failed fix**: `loadTranslations('procest', callback)` before mount caused **all text to display empty** — reverted +- **Current**: App uses `Vue.mixin({ methods: { t, n } })` with global `t` and `n` + +--- + +## Technical Background + +### @nextcloud/l10n + +- **getLocale()**: `document.documentElement.dataset.locale || 'en'` +- **Translations**: `window._oc_l10n_registry_translations[appId]` +- **loadTranslations(appName, callback)**: Fetches `l10n/{locale}.json`, parses, calls `register(appName, result.translations)`. For locale 'en', resolves immediately without fetch. +- **register(appName, bundle)**: Sets `window._oc_l10n_registry_translations[appId] = bundle` + +### Procest app flow + +1. DashboardController returns `TemplateResponse(Application::APP_ID, 'index')` +2. Nextcloud renders `templates/index.php` (exists): `Util::addScript($appId, $appId . '-main')` + `
` +3. Page loads `procest-main.js` (webpack bundle) +4. main.js: `Vue.mixin({ methods: { t, n } })` then `new Vue(...).$mount('#content')` +5. Components call `this.t('procest', key)` → `t('procest', key)` (from mixin) + +**Template finding**: `templates/index.php` does not explicitly load l10n. It only adds the main script. Nextcloud core may inject app l10n automatically when rendering the page — or it may not for app pages. 1.2/1.3 will tell us. + +### Where do global t, n come from? + +The app does not import `t` or `n`. They are used as globals. Nextcloud core typically injects these via the page. For app pages, the core may inject `OC.L10n` or similar. The `@nextcloud/l10n` package exports `t`, `n` that use `window._oc_l10n_registry_translations`. If the core injects a different `t` that uses a different registry, there could be a mismatch. + +--- + +## Root Cause Hypotheses + +| ID | Hypothesis | How to verify | +|----|------------|---------------| +| H1 | Server does not inject app translations for Procest | Inspect rendered HTML; check for l10n script tags | +| H2 | App template missing or does not load l10n | Check if `templates/index.php` exists; check default template | +| H3 | Global t vs @nextcloud/l10n t mismatch | Import t from @nextcloud/l10n; use loadTranslations before mount | +| H4 | document.documentElement.dataset.locale not set | DevTools: inspect `document.documentElement.dataset` when locale is Dutch | +| H5 | loadTranslations callback timing — Vue used stale t | Ensure t from @nextcloud/l10n; mount only after callback | + +--- + +## Investigation Checklist + +- [x] Check if `templates/index.php` exists in Procest — **YES** (adds procest-main.js, div#content) +- [x] In browser (Nextcloud set to Dutch): `document.documentElement.dataset.locale` — **returns "nl"** ✓ +- [x] In browser: `window._oc_l10n_registry_translations` — **procest is undefined** (server does not inject app l10n) +- [ ] Compare with working apps — optional; root cause confirmed +- [ ] Try: import t,n from @nextcloud/l10n + loadTranslations before mount; verify no empty text + +--- + +## Investigation Results (2026-03-03) + +| Check | Result | Meaning | +|-------|--------|---------| +| 1.2 Locale | `"nl"` | Nextcloud correctly sets locale; user language is Dutch | +| 1.3 Registry | `procest` undefined | **Root cause**: Nextcloud server does NOT inject Procest's translations into `window._oc_l10n_registry_translations` | + +**Conclusion**: The server does not load Procest's l10n for app pages. The app must load its own translations. Use `loadTranslations('procest', callback)` before mount, and import `t`, `n` from `@nextcloud/l10n` (not globals). Proceed to implement fix (section 2). + +**Why previous loadTranslations caused empty text**: The app used global `t`/`n` (from Nextcloud core). Those may use a different registry or expect server-injected data. By importing `t`/`n` from `@nextcloud/l10n` and passing them to the mixin, we use the same implementation that `loadTranslations` registers with. Mount only after the callback so the registry is populated before any component renders. + +--- + +## How to Inspect Locale and Registry (1.2, 1.3) + +**1.2 Locale**: Open Procest with Nextcloud set to Nederlands. In DevTools Console: +```javascript +document.documentElement.dataset.locale // expect "nl" or "nl_NL" +document.documentElement.lang // expect "nl" +``` + +**1.3 Registry**: In DevTools Console: +```javascript +window._oc_l10n_registry_translations +``` +Expand the object. Check if `procest` exists and has Dutch strings. If missing/empty, server is not injecting app l10n. + +--- + +## Reference Apps (1.4) + +| App | URL | Pattern | +|-----|-----|---------| +| Deck | https://github.com/nextcloud/deck/blob/main/src/main.js | `import { translate, translatePlural } from '@nextcloud/l10n'`; `Vue.prototype.t = translate`; no loadTranslations | +| Files | nextcloud/server/apps/files | Check src for l10n usage | + +--- + +## Empty Text Bug — Root Cause (2026-03-03) + +**Symptom**: After loadTranslations fix, `window._oc_l10n_registry_translations.procest` has Dutch translations, but frontend shows no words. + +**Root cause**: `@nextcloud/l10n` exports `translate` and `translatePlural`, NOT `t` and `n`. The import `{ t, n, loadTranslations }` was resolving `t` and `n` to `undefined`. The Vue mixin received `methods: { t: undefined, n: undefined }`, so `this.t()` in components failed (undefined is not a function). + +**Fix**: Use the correct import alias: +```javascript +import { translate as t, translatePlural as n, loadTranslations } from '@nextcloud/l10n' +``` + +--- + +## References + +- complete-l10n exploration: `archive/2026-03-03-complete-l10n/exploration.md` +- @nextcloud/l10n: https://nextcloud-libraries.github.io/nextcloud-l10n/ +- Nextcloud translations: https://docs.nextcloud.com/server/latest/developer_manual/basics/translations.html diff --git a/openspec/changes/archive/2026-03-03-fix-dutch-display/proposal.md b/openspec/changes/archive/2026-03-03-fix-dutch-display/proposal.md new file mode 100644 index 0000000..f86f3cd --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-dutch-display/proposal.md @@ -0,0 +1,54 @@ +# Proposal: Fix Displaying Dutch Language + +## Summary + +Fix the Procest app so that Dutch translations are displayed when the user's Nextcloud language is set to Nederlands. The l10n files (`l10n/en.json`, `l10n/nl.json`) are complete with 302 keys (complete-l10n change, archived). However, the app displays English regardless of locale. This change addresses the translation loading/locale injection so Dutch texts appear correctly. + +## Problem + +**User-reported symptom**: When Nextcloud language is set to Nederlands, the Procest app still displays almost entirely in English. + +**Context**: The complete-l10n change added all missing keys to both l10n files. Automated verification passed (JSON valid, keys in sync, placeholders preserved). Manual Dutch verification failed — Dutch texts are not shown. + +**Known facts**: +- `l10n/nl.json` contains 302 keys with correct Dutch translations +- The app uses `t('procest', '...')` throughout; `t` and `n` are provided via `Vue.mixin({ methods: { t, n } })` (globals) +- A previous attempt to use `loadTranslations('procest', callback)` from `@nextcloud/l10n` before mount caused **all text to display empty** — reverted +- Root cause is unclear: may be server-side (locale injection, app template) or app-side (translation registration timing) + +## Scope — MVP + +**In scope:** +- Investigate and fix why Dutch translations are not displayed +- Ensure `t('procest', key)` returns Dutch when user locale is Nederlands +- Preserve existing l10n files (no changes to en.json/nl.json content) + +**Out of scope:** +- Adding new translation keys (already done in complete-l10n) +- Additional languages beyond en/nl +- Changing translation strings + +## Approach + +1. **Investigate** how Nextcloud injects locale and translations for app pages (TemplateResponse, app template, `document.documentElement.dataset.locale`, `window._oc_l10n_registry_translations`) +2. **Identify** whether the app template loads app l10n, or if the app must load it itself +3. **Fix** translation loading: either (a) ensure server injects app translations; (b) use `@nextcloud/l10n` correctly (debug why loadTranslations caused empty text); or (c) register translations before Vue mount with correct timing +4. **Verify** manually: set Nextcloud to Nederlands, confirm dashboard and key views show Dutch + +## Capabilities + +### Modified Capabilities + +- **localization**: Dutch translations will actually display when user language is Nederlands. Completes the localization story started in complete-l10n. + +## Impact + +- **Frontend**: Likely `src/main.js`, `src/settings.js` (translation loading); possibly app template if server-side +- **Backend**: Possibly app template (templates/index.php) if Nextcloud requires explicit l10n injection +- **Dependencies**: @nextcloud/l10n, Nextcloud core translation system + +## Dependencies + +- complete-l10n (archived) — l10n files are complete; this change fixes display +- admin-settings spec (localization requirement) +- Nextcloud core (locale, app template) diff --git a/openspec/changes/archive/2026-03-03-fix-dutch-display/tasks.md b/openspec/changes/archive/2026-03-03-fix-dutch-display/tasks.md new file mode 100644 index 0000000..107cd99 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-dutch-display/tasks.md @@ -0,0 +1,31 @@ +# Tasks: Fix Displaying Dutch Language + +## 1. Investigate + +- [x] 1.1 Check if `templates/index.php` exists — **YES**, exists at `templates/index.php` (adds procest-main.js, div#content) +- [x] 1.2 In browser (Nextcloud set to Nederlands): inspect locale — **returns "nl"** ✓ +- [x] 1.3 In browser: inspect translation registry — **procest is undefined** (server does not inject app l10n) +- [x] 1.4 Compare with working Nextcloud apps — optional; root cause confirmed + +**Root cause**: Server does not inject Procest's l10n. App must load its own via `loadTranslations`. Proceed to section 2. + +## 2. Implement Fix + +- [x] 2.1 Import `translate as t`, `translatePlural as n`, `loadTranslations` from `@nextcloud/l10n` in `src/main.js` +- [x] 2.2 Wrap Vue mount in `loadTranslations('procest', () => { ... })` callback +- [x] 2.3 Pass imported `t`, `n` to `Vue.mixin({ methods: { t, n } })` (not globals) +- [x] 2.4 Apply same pattern to `src/settings.js` if it uses t/n +- [x] 2.5 If loadTranslations causes empty text: **FIXED** — import was wrong: use `translate as t, translatePlural as n` (package exports `translate`/`translatePlural`, not `t`/`n`) + +## 3. Verify + +- [x] 3.1 Set Nextcloud language to Nederlands; open Procest app +- [x] 3.2 Verify dashboard labels (Dashboard, New Case, New Task, etc.) show Dutch +- [x] 3.3 Verify case list, case detail, task forms show Dutch labels +- [x] 3.4 Verify English still works when locale is en +- [x] 3.5 Rebuild app (`npm run build`); hard refresh browser + +## 4. Fallback (if app-side fix fails) + +- [ ] 4.1 Create or update `templates/index.php` to explicitly load app l10n (if Nextcloud supports this) +- [ ] 4.2 Document findings for next investigation cycle diff --git a/openspec/changes/archive/2026-03-03-fix-dutch-display/verify-report.md b/openspec/changes/archive/2026-03-03-fix-dutch-display/verify-report.md new file mode 100644 index 0000000..82a838c --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-dutch-display/verify-report.md @@ -0,0 +1,34 @@ +# Verification Report: Fix Displaying Dutch Language + +**Date**: 2026-03-03 +**Change**: fix-dutch-display (archived) +**Status**: PASSED — automated and manual checks complete + +--- + +## Automated Checks + +| Check | Result | +|-------|--------| +| en.json valid JSON | OK | +| nl.json valid JSON | OK | +| en.json and nl.json identical keys | OK (302 total) | +| Placeholders preserved in nl.json | OK (36 keys) | +| All keys used in code exist in l10n | OK (275 keys) | + +--- + +## Manual Verification (fix-dutch-display) + +| Task | Status | +|------|--------| +| V01: Set Nextcloud to Nederlands; verify dashboard labels show Dutch | OK | +| V02: Verify case list, case detail, task forms show Dutch | OK | +| V03: Verify English still works when locale is en | OK | + +--- + +## Verification Scripts + +- **L10n (automated)**: `node openspec/verify-l10n.js` from procest app root +- **Manual**: Set Nextcloud language, open Procest, confirm Dutch/English display diff --git a/openspec/changes/archive/2026-03-03-fix-settings-bug/.openspec.yaml b/openspec/changes/archive/2026-03-03-fix-settings-bug/.openspec.yaml new file mode 100644 index 0000000..ed9e94a --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-settings-bug/.openspec.yaml @@ -0,0 +1,3 @@ +schema: conduction +created: 2026-03-03 +status: archived diff --git a/openspec/changes/archive/2026-03-03-fix-settings-bug/design.md b/openspec/changes/archive/2026-03-03-fix-settings-bug/design.md new file mode 100644 index 0000000..e67fa06 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-settings-bug/design.md @@ -0,0 +1,80 @@ +# Design: Fix Settings Bug + +## Context + +The Procest app uses `MainMenu.vue` for navigation. The footer contains `NcAppNavigationSettings`, which renders a button (showing "Instellingen" when Nextcloud is in Dutch) and a dropdown with two items: "Case Types" (→ `/case-types`) and "Configuration" (→ `/settings`). Both routes render `AdminRoot.vue` — the same page with Configuration and Case Type Management sections. The dropdown is redundant and the button label uses Nextcloud's locale instead of the app's. + +## Goals / Non-Goals + +**Goals:** +- Single "Settings" button in the footer that navigates directly to the admin page (one click, no dropdown) +- Button label uses `t('procest', 'Settings')` for proper app l10n +- Remove redundant navigation options + +**Non-Goals:** +- Changes to AdminRoot, Configuration, or Case Type Management content +- Backend changes + +## File Map + +### Modified Files + +| File | Changes | +|------|---------| +| `src/navigation/MainMenu.vue` | Replace NcAppNavigationSettings (dropdown) with single NcAppNavigationItem in footer; use t('procest', 'Settings') | +| `l10n/en.json` | Add "Settings" translation if missing | +| `l10n/nl.json` | Add "Instellingen" (or "Settings") for Dutch if missing | +| `src/router/index.js` | Remove `/case-types` route (optional — keeps URL tidy; or keep for backwards compatibility) | + +### Unchanged Files + +| File | Reason | +|------|--------| +| `src/views/settings/AdminRoot.vue` | No changes | +| All other views | No changes | + +## Design Decisions + +### DD-01: Single NcAppNavigationItem in Footer + +**Decision**: Replace `NcAppNavigationSettings` with a single `NcAppNavigationItem` in the footer slot. + +**Rationale**: NcAppNavigationSettings creates a dropdown. With one destination, a dropdown adds an extra click. A direct NcAppNavigationItem gives one-click access. The NcAppNavigation footer slot accepts arbitrary content; NcAppNavigationItem can be used there. + +**Implementation**: Put one NcAppNavigationItem with `:to="{ name: 'Settings' }"` and `:name="t('procest', 'Settings')"` in the footer template. + +### DD-02: Keep or Remove /case-types Route + +**Decision**: Remove the `/case-types` route from the router; use only `/settings`. + +**Rationale**: Both routes render AdminRoot. Having two URLs for the same page is confusing. External links or bookmarks to `/case-types` would break — but this is a small app; such links are unlikely. Simpler to have one canonical URL. + +**Alternative**: Keep both routes and redirect `/case-types` → `/settings` for backwards compatibility. Prefer removal for simplicity unless backwards compatibility is required. + +### DD-03: Localization + +**Decision**: Use `t('procest', 'Settings')` for the button label. Add "Settings" to l10n files. + +**Rationale**: Procest has l10n/en.json and l10n/nl.json. The `t()` function uses the app's translation files. "Settings" in English; "Instellingen" in Dutch. Ensures the label matches the app's locale. + +## Component Change + +``` +Before: + NcAppNavigationSettings (button: "Instellingen" / framework locale) + ├── NcAppNavigationItem "Case Types" → /case-types (AdminRoot) + └── NcAppNavigationItem "Configuration" → /settings (AdminRoot) + +After: + NcAppNavigationItem "Settings" → /settings (AdminRoot) + (single item in footer, no dropdown) +``` + +## Risks / Trade-offs + +- **[Risk] NcAppNavigation footer slot structure** → The footer may expect NcAppNavigationSettings specifically. Mitigation: Check Nextcloud Vue docs; if needed, use NcAppNavigationSettings with one item and pass `:name="t('procest', 'Settings')"` to override the button label. +- **[Trade-off] Removing /case-types** → Any existing links to `/case-types` would 404. Low risk for a settings page. + +## Open Questions + +None — the fix is straightforward. diff --git a/openspec/changes/archive/2026-03-03-fix-settings-bug/proposal.md b/openspec/changes/archive/2026-03-03-fix-settings-bug/proposal.md new file mode 100644 index 0000000..c4d859e --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-settings-bug/proposal.md @@ -0,0 +1,45 @@ +# Proposal: Fix Settings Bug + +## Summary + +Fix two related UI bugs in the Procest app navigation: (1) the settings button shows "Instellingen" (Dutch) instead of using proper localization; (2) the settings dropdown has two options — "Case Types" and "Configuration" — that both navigate to the same page (AdminRoot). Replace with a single "Settings" button that goes directly to the admin page. + +## Problem + +1. **Wrong language**: The settings button in the app footer shows "Instellingen" (Dutch) when it should use the app's locale (English by default, or user's language via l10n). +2. **Redundant dropdown**: Clicking the settings button opens a dropdown with "Case Types" and "Configuration". Both options route to the same component (AdminRoot at `/settings` and `/case-types`). Users must click twice to reach settings; one direct button is sufficient. + +## Scope — MVP + +**In scope:** +- Replace the settings dropdown with a single "Settings" button that navigates directly to the admin page +- Use proper localization: `t('procest', 'Settings')` so the label respects the app's l10n (en.json, nl.json) +- Remove the redundant `/case-types` route or consolidate routing (both currently render AdminRoot) + +**Out of scope:** +- New features or requirements +- Changes to the admin page content (Configuration + Case Type Management sections remain as-is) + +## Approach + +1. In `MainMenu.vue`: Replace `NcAppNavigationSettings` (dropdown with two items) with a single `NcAppNavigationItem` in the footer that links directly to the Settings route +2. Use `t('procest', 'Settings')` for the button label +3. Add "Settings" to `l10n/en.json` and `l10n/nl.json` if not present +4. Remove the redundant `/case-types` route from the router (or keep for backwards compatibility but remove from nav) + +## Capabilities + +### Modified Capabilities + +- **admin-settings**: UI/navigation fix — single settings entry point, proper localization. No spec changes. + +## Impact + +- **Frontend**: `src/navigation/MainMenu.vue`, `src/router/index.js`, `l10n/en.json`, `l10n/nl.json` +- **Backend**: None +- **Dependencies**: Nextcloud Vue (NcAppNavigationItem), existing l10n + +## Dependencies + +- admin-settings spec (navigation is part of admin access) +- Nextcloud Vue components diff --git a/openspec/changes/archive/2026-03-03-fix-settings-bug/specs/admin-settings/spec.md b/openspec/changes/archive/2026-03-03-fix-settings-bug/specs/admin-settings/spec.md new file mode 100644 index 0000000..4aef562 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-settings-bug/specs/admin-settings/spec.md @@ -0,0 +1,36 @@ +# Delta Spec: Admin Settings — Settings Navigation Bug Fix + +**Source**: `openspec/specs/admin-settings/spec.md` +**Scope**: UI/navigation fix — single settings button, proper localization. No spec changes to admin-settings requirements. + +--- + +## ADDED Requirements (implicit from fix) + +### REQ-NAV-001: Settings Navigation [MVP] + +The app MUST provide a single, clearly labeled entry point to the admin settings page from the main navigation. + +#### Scenario: Settings button in navigation footer +- **WHEN** the user views the Procest app +- **THEN** a "Settings" (or localized equivalent) button MUST appear in the navigation footer +- **AND** clicking it MUST navigate directly to the admin settings page (one click, no dropdown) + +#### Scenario: Settings label uses app locale +- **WHEN** the app locale is English +- **THEN** the settings button MUST display "Settings" +- **WHEN** the app locale is Dutch +- **THEN** the settings button MUST display "Instellingen" (or the Dutch translation) + +#### Scenario: No redundant navigation options +- **WHEN** the user clicks the settings button +- **THEN** the system MUST NOT show a dropdown with multiple options that lead to the same page +- **AND** the user MUST reach the admin page with a single click + +--- + +## EXCLUDED Requirements + +| Req | Description | Reason | +|-----|-------------|--------| +| REQ-ADMIN-001 through REQ-ADMIN-016 | Admin settings content and behavior | This change only fixes navigation; admin page content unchanged | diff --git a/openspec/changes/archive/2026-03-03-fix-settings-bug/tasks.md b/openspec/changes/archive/2026-03-03-fix-settings-bug/tasks.md new file mode 100644 index 0000000..a576985 --- /dev/null +++ b/openspec/changes/archive/2026-03-03-fix-settings-bug/tasks.md @@ -0,0 +1,16 @@ +# Tasks: Fix Settings Bug + +## 1. Navigation Fix [MVP] + +- [x] 1.1 Replace NcAppNavigationSettings with single NcAppNavigationItem in MainMenu.vue footer — use `:name="t('procest', 'Settings')"` and `:to="{ name: 'Settings' }"`, include Cog icon +- [x] 1.2 Add "Settings" to l10n/en.json and "Instellingen" to l10n/nl.json (if not already present) + +## 2. Route Cleanup + +- [x] 2.1 Remove `/case-types` route from router (or add redirect to `/settings` for backwards compatibility) + +## Verification Tasks + +- [x] V01 Manual test: Settings button appears in footer with correct label (English: "Settings", Dutch: "Instellingen") +- [x] V02 Manual test: Single click on Settings navigates to admin page (no dropdown) +- [x] V03 Manual test: Admin page shows both Configuration and Case Type Management sections diff --git a/openspec/config.yaml b/openspec/config.yaml index 28fd5a0..bb6862f 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -53,3 +53,6 @@ rules: - Test with OpenRegister to verify schema validation works - Verify Nextcloud integration features with actual OCP interfaces - Test request-to-case bridge with Pipelinq + review: + - Cross-reference shared specs (nextcloud-app, api-patterns, nl-design, docker) + - Flag spec deviations with WARNING and justification diff --git a/openspec/setup-schemas.ps1 b/openspec/setup-schemas.ps1 new file mode 100644 index 0000000..bd55c79 --- /dev/null +++ b/openspec/setup-schemas.ps1 @@ -0,0 +1,22 @@ +# Create junction so procest/openspec/schemas points to shared apps-extra/openspec/schemas +# Run from procest root: .\openspec\setup-schemas.ps1 + +$schemasDir = Join-Path $PSScriptRoot "schemas" +$targetDir = (Resolve-Path (Join-Path $PSScriptRoot "..\..\openspec\schemas") -ErrorAction SilentlyContinue) + +if (-not $targetDir) { + Write-Error "Shared schemas not found at apps-extra/openspec/schemas. Ensure nextcloud-docker-dev workspace is set up." + exit 1 +} + +if (Test-Path $schemasDir) { + $item = Get-Item $schemasDir + if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { + Write-Host "Junction already exists: $schemasDir" + exit 0 + } + Remove-Item -Recurse -Force $schemasDir +} + +cmd /c mklink /J $schemasDir $targetDir.Path +Write-Host "Created junction: $schemasDir -> $($targetDir.Path)" diff --git a/openspec/verify-l10n.js b/openspec/verify-l10n.js new file mode 100644 index 0000000..ceb792c --- /dev/null +++ b/openspec/verify-l10n.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node +/** + * Verify l10n: JSON valid, key sync, placeholders, code coverage. + * Run from procest app root: node openspec/verify-l10n.js + */ +const fs = require('fs') +const path = require('path') + +const L10N_DIR = path.join(__dirname, '..', 'l10n') +const SRC_DIR = path.join(__dirname, '..', 'src') + +function loadJson(file) { + try { + const raw = fs.readFileSync(file, 'utf8') + const data = JSON.parse(raw) + return { data, error: null } + } catch (e) { + return { data: null, error: e.message } + } +} + +function main() { + let failed = false + + // 1. Load and validate JSON + const enPath = path.join(L10N_DIR, 'en.json') + const nlPath = path.join(L10N_DIR, 'nl.json') + + const enResult = loadJson(enPath) + const nlResult = loadJson(nlPath) + + if (enResult.error) { + console.error('FAIL: en.json parsing error:', enResult.error) + failed = true + } else { + console.log('OK: en.json valid JSON') + } + + if (nlResult.error) { + console.error('FAIL: nl.json parsing error:', nlResult.error) + failed = true + } else { + console.log('OK: nl.json valid JSON') + } + + if (failed) process.exit(1) + + const enKeys = new Set(Object.keys(enResult.data.translations)) + const nlKeys = new Set(Object.keys(nlResult.data.translations)) + + // 2. REQ-L10N-001: Identical key sets + const onlyInEn = [...enKeys].filter(k => !nlKeys.has(k)) + const onlyInNl = [...nlKeys].filter(k => !enKeys.has(k)) + + if (onlyInEn.length > 0) { + console.error('FAIL: Keys in en.json but not nl.json:', onlyInEn.slice(0, 5).join(', '), onlyInEn.length > 5 ? `... (+${onlyInEn.length - 5} more)` : '') + failed = true + } + if (onlyInNl.length > 0) { + console.error('FAIL: Keys in nl.json but not en.json:', onlyInNl.slice(0, 5).join(', '), onlyInNl.length > 5 ? `... (+${onlyInNl.length - 5} more)` : '') + failed = true + } + if (onlyInEn.length === 0 && onlyInNl.length === 0) { + console.log('OK: en.json and nl.json have identical keys (' + enKeys.size + ' total)') + } + + // 3. Placeholder preservation + const placeholderKeys = [...enKeys].filter(k => /\{[a-zA-Z]+\}/.test(enResult.data.translations[k])) + let placeholderOk = true + for (const key of placeholderKeys) { + const enVal = enResult.data.translations[key] + const nlVal = nlResult.data.translations[key] + const enPlaceholders = (enVal.match(/\{[a-zA-Z]+\}/g) || []).sort().join(',') + const nlPlaceholders = (nlVal.match(/\{[a-zA-Z]+\}/g) || []).sort().join(',') + if (enPlaceholders !== nlPlaceholders) { + console.error('FAIL: Placeholder mismatch for key:', key, '| en:', enPlaceholders, '| nl:', nlPlaceholders) + placeholderOk = false + failed = true + } + } + if (placeholderOk && placeholderKeys.length > 0) { + console.log('OK: Placeholders preserved in nl.json for', placeholderKeys.length, 'keys') + } + + // 4. Extract keys from t('procest', ...) in src + function walkDir(dir, files = []) { + const entries = fs.readdirSync(dir, { withFileTypes: true }) + for (const e of entries) { + const full = path.join(dir, e.name) + if (e.isDirectory() && e.name !== 'node_modules') { + walkDir(full, files) + } else if (e.isFile() && (e.name.endsWith('.vue') || e.name.endsWith('.js'))) { + files.push(full) + } + } + return files + } + const srcFiles = walkDir(SRC_DIR) + + function unescapeKey(s) { + return s.replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))) + } + + const usedKeys = new Set() + const tPatternSingle = /t\s*\(\s*['"]procest['"]\s*,\s*'((?:[^'\\]|\\.)*)'/g + const tPatternDouble = /t\s*\(\s*['"]procest['"]\s*,\s*"((?:[^"\\]|\\.)*)"/g + for (const file of srcFiles) { + try { + const content = fs.readFileSync(file, 'utf8') + let m + tPatternSingle.lastIndex = 0 + while ((m = tPatternSingle.exec(content)) !== null) { + usedKeys.add(unescapeKey(m[1])) + } + tPatternDouble.lastIndex = 0 + while ((m = tPatternDouble.exec(content)) !== null) { + usedKeys.add(unescapeKey(m[1])) + } + } catch (_) {} + } + + const missingInL10n = [...usedKeys].filter(k => !enKeys.has(k)) + if (missingInL10n.length > 0) { + console.error('FAIL: Keys used in code but missing from l10n:', missingInL10n.slice(0, 10).join(', '), missingInL10n.length > 10 ? `... (+${missingInL10n.length - 10} more)` : '') + failed = true + } else { + console.log('OK: All', usedKeys.size, 'keys used in code exist in l10n') + } + + // Orphan check: keys in l10n not used in code (original 55 are allowed) + const originalKeys = new Set([ + 'Procest', 'Dashboard', 'Cases', 'Case', 'Tasks', 'Task', 'New case', 'New task', + 'Title', 'Description', 'Status', 'Assignee', 'Priority', 'Due date', 'Created', 'Updated', + 'Closed', 'Save', 'Cancel', 'Delete', 'Edit', 'Search', 'Loading...', 'No cases found', + 'No tasks found', 'Are you sure you want to delete this?', 'Case created successfully', + 'Case updated successfully', 'Case deleted successfully', 'Task created successfully', + 'Task updated successfully', 'Settings', 'Register', 'Case schema', 'Task schema', + 'Status schema', 'Role schema', 'Result schema', 'Decision schema', 'Configuration saved', + 'Welcome to Procest', 'Manage your cases and tasks', 'open', 'in_progress', 'closed', + 'low', 'normal', 'high', 'urgent', 'Back to list', 'Previous', 'Next' + ]) + const orphanKeys = [...enKeys].filter(k => !usedKeys.has(k) && !originalKeys.has(k)) + if (orphanKeys.length > 0) { + console.warn('WARN: Keys in l10n not found in code (may be used dynamically):', orphanKeys.length) + } + + console.log('') + if (failed) { + console.error('VERIFICATION FAILED') + process.exit(1) + } + console.log('VERIFICATION PASSED') +} + +main() diff --git a/phpstan-bootstrap.php b/phpstan-bootstrap.php new file mode 100644 index 0000000..0203324 --- /dev/null +++ b/phpstan-bootstrap.php @@ -0,0 +1,33 @@ + __DIR__ . '/vendor/nextcloud/ocp/OCP/', + 'NCU\\' => __DIR__ . '/vendor/nextcloud/ocp/NCU/', + ]; + + foreach ($prefixMap as $prefix => $dir) { + if (strncmp($class, $prefix, strlen($prefix)) !== 0) { + continue; + } + + $relative = str_replace('\\', '/', substr($class, strlen($prefix))); + $file = $dir . $relative . '.php'; + if (file_exists($file)) { + require_once $file; + } + break; + } +}); diff --git a/phpstan.neon b/phpstan.neon index b70c0fa..5a0707c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,6 +4,7 @@ parameters: - lib bootstrapFiles: - vendor/autoload.php + - phpstan-bootstrap.php excludePaths: - vendor scanDirectories: @@ -14,3 +15,15 @@ parameters: - '#Call to an undefined method OC::#' - '#Class OC not found#' - '#Access to static property \$server on an unknown class OC#' + # OCP (nextcloud/ocp has no composer autoload; bootstrap + scanDirectories may not resolve in all environments) + - '#extends unknown class OCP\\#' + - '#implements unknown interface OCP\\#' + - '#has invalid type OCP\\#' + - '#has unknown class OCP\\#' + - '#on an unknown class OCP\\#' + - '#Instantiated class OCP\\#' + - '#calls parent::__construct\(\) but .+ does not extend any class#' + # OpenRegister (runtime app dependency, not in composer) + - '#OCA\\OpenRegister\\#' + # Controller::$request from OCP\AppFramework\Controller + - '#Access to an undefined property OCA\\Procest\\Controller\\SettingsController::\$request#' diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..5eecd85 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + + ./tests/unit + + + + + ./lib + + + diff --git a/psalm.xml b/psalm.xml index 7c25db5..7d98f75 100644 --- a/psalm.xml +++ b/psalm.xml @@ -59,8 +59,11 @@ + + + diff --git a/src/main.js b/src/main.js index 0f88ef3..ea0bd75 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,6 @@ import Vue from 'vue' import { PiniaVuePlugin } from 'pinia' +import { translate as t, translatePlural as n, loadTranslations } from '@nextcloud/l10n' import pinia from './pinia.js' import router from './router/index.js' import App from './App.vue' @@ -10,8 +11,10 @@ import '@conduction/nextcloud-vue/css/index.css' Vue.mixin({ methods: { t, n } }) Vue.use(PiniaVuePlugin) -new Vue({ - pinia, - router, - render: h => h(App), -}).$mount('#content') +loadTranslations('procest', () => { + new Vue({ + pinia, + router, + render: h => h(App), + }).$mount('#content') +}) diff --git a/src/navigation/MainMenu.vue b/src/navigation/MainMenu.vue index df200e9..fb9b04e 100644 --- a/src/navigation/MainMenu.vue +++ b/src/navigation/MainMenu.vue @@ -38,49 +38,37 @@