Compare commits

..
3 Commits
Author SHA1 Message Date
magdevandClaude Opus 4.6 3ac1e0d6f7 Add custom page template for composable products, bump to v1.3.2
Create Release Package / PHP Lint (push) Successful in 48s
Create Release Package / PHP CodeSniffer (push) Successful in 1m0s
Create Release Package / PHP Unit (push) Successful in 59s
Create Release Package / build-release (push) Successful in 1m4s
- Custom WooCommerce template with compact header + full-width selector
- Twig layout template (single-product-composable.html.twig) + PHP loader
- Body class 'single-product-composable' for CSS scoping
- Renamed *.twig to *.html.twig (proper naming convention)
- Refreshed .pot with accurate file refs, merged all .po files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:08:44 +01:00
magdev 8877ce976a ignore local symlinks 2026-03-01 13:41:31 +01:00
magdevandClaude Opus 4.6 dea1b055b2 Upgrade to PHPUnit 10, add PHPCS with WPCS compliance, add phpcs CI job
Create Release Package / PHP Lint (push) Successful in 48s
Create Release Package / PHP CodeSniffer (push) Successful in 52s
Create Release Package / PHP Unit (push) Successful in 53s
Create Release Package / build-release (push) Successful in 59s
- Upgrade PHPUnit 9.6 → 10, update phpunit.xml.dist schema
- Add PHPCS 3.13 with WordPress-Extra + PHPCompatibilityWP standards
- PHPCBF auto-fix + manual fixes for full WPCS compliance
- Add phpcs job to release workflow (parallel with lint)
- Pin composer platform to PHP 8.3 to prevent incompatible dep locks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:25:02 +01:00
27 changed files with 3535 additions and 2315 deletions

No files matched your search

+24 -1
View File
@@ -28,7 +28,30 @@ jobs:
find includes -name "*.php" -print0 | xargs -0 -n1 php -l find includes -name "*.php" -print0 | xargs -0 -n1 php -l
find tests -name "*.php" -print0 | xargs -0 -n1 php -l find tests -name "*.php" -print0 | xargs -0 -n1 php -l
phpcs:
name: PHP CodeSniffer
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, xml
tools: composer:v2
- name: Install Composer dependencies
run: |
composer config platform.php 8.3.0
composer install --optimize-autoloader --no-interaction
- name: Run PHPCS
run: vendor/bin/phpcs
test: test:
name: PHP Unit
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: lint needs: lint
steps: steps:
@@ -51,7 +74,7 @@ jobs:
run: vendor/bin/phpunit --testdox run: vendor/bin/phpunit --testdox
build-release: build-release:
needs: test needs: [test, phpcs]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
+3
View File
@@ -11,6 +11,8 @@ cache/
.vscode/ .vscode/
.idea/ .idea/
logs/ logs/
wp-core
wc-core
*.log *.log
# OS files # OS files
@@ -25,3 +27,4 @@ phpunit.xml
languages/*.mo languages/*.mo
.phpunit.result.cache .phpunit.result.cache
.phpunit.cache/
+22 -3
View File
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.3.2] - 2026-03-01
### Added
- **Custom page template** for composable products — replaces the standard WooCommerce two-column layout (large image gallery + summary) with a compact product info header and full-width product selector grid
- New Twig template `single-product-composable.html.twig` with PHP loader for WooCommerce template override
- Body class `single-product-composable` for CSS scoping on composable product pages
### Changed
- Renamed Twig templates from `*.twig` to `*.html.twig` (proper Twig naming convention)
- Refreshed translation catalog (.pot) with accurate file references and line numbers
- Updated all .po/.mo translation files via `msgmerge`
## [1.3.1] - 2026-03-01 ## [1.3.1] - 2026-03-01
### Added ### Added
@@ -12,15 +26,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **PHPUnit test suite**: 57 unit tests covering all 6 core classes (ProductType, StockManager, CartHandler, Plugin, Admin/ProductData, Admin/Settings) - **PHPUnit test suite**: 57 unit tests covering all 6 core classes (ProductType, StockManager, CartHandler, Plugin, Admin/ProductData, Admin/Settings)
- **Brain Monkey + Mockery** for WordPress/WooCommerce function mocking without a full WP installation - **Brain Monkey + Mockery** for WordPress/WooCommerce function mocking without a full WP installation
- **WooCommerce class stubs** in `tests/stubs/` for classes extended by the plugin (WC_Product, WC_Settings_Page, etc.) - **WooCommerce class stubs** in `tests/stubs/` for classes extended by the plugin (WC_Product, WC_Settings_Page, etc.)
- **PHPCS** with WordPress-Extra and PHPCompatibilityWP coding standards (`phpcs.xml.dist`)
- **PHPCS job** in release workflow — coding standards must pass before release is built
- **PHP lint job** in release workflow — syntax-checks all PHP files before testing - **PHP lint job** in release workflow — syntax-checks all PHP files before testing
- **Test job** in release workflow — tests must pass before release package is built (`needs: test`) - **Test job** in release workflow — tests must pass before release package is built
- Testing section in README - Testing and linting sections in README and CLAUDE.md
### Changed ### Changed
- **PSR-4 refactored**: Renamed files to PascalCase (Product_Type → ProductType, etc.) and changed namespace from `WC_Composable_Product` to `Magdev\WcComposableProduct` - **PSR-4 refactored**: Renamed files to PascalCase (Product_Type → ProductType, etc.) and changed namespace from `WC_Composable_Product` to `Magdev\WcComposableProduct`
- Updated all cross-references in PHP files, main plugin file, composer.json, CSS/JS doc comments, and translation file source comments - Updated all cross-references in PHP files, main plugin file, composer.json, CSS/JS doc comments, and translation file source comments
- Release workflow now has three stages: lint → test → build-release - **PHPUnit upgraded** from 9.6 to 10 (Brain Monkey 2.7 supports both)
- **WPCS formatting** applied to all source files (tabs, Yoda conditions, strict `in_array`, `wp_json_encode`, long array syntax)
- Release workflow now has four stages: lint + phpcs (parallel) → test → build-release
- Composer platform pinned to PHP 8.3 to prevent incompatible dependency locks
## [1.3.0] - 2026-03-01 ## [1.3.0] - 2026-03-01
+9 -3
View File
@@ -18,7 +18,8 @@ This project is 100% AI-generated ("vibe-coded") using Claude.AI.
- **Styling:** Custom CSS - **Styling:** Custom CSS
- **Dependencies:** Composer - **Dependencies:** Composer
- **i18n:** WordPress i18n (.pot/.po/.mo), text domain: `wc-composable-product` - **i18n:** WordPress i18n (.pot/.po/.mo), text domain: `wc-composable-product`
- **Testing:** PHPUnit 9.6 + Brain Monkey 2.7 + Mockery 1.6 - **Testing:** PHPUnit 10 + Brain Monkey 2.7 + Mockery 1.6
- **Linting:** PHPCS 3.13 with WordPress-Extra + PHPCompatibilityWP
- **CI/CD:** Gitea Actions (`.gitea/workflows/release.yml`) - **CI/CD:** Gitea Actions (`.gitea/workflows/release.yml`)
## Project Structure ## Project Structure
@@ -55,6 +56,7 @@ wc-composable-product/
│ └── product-selector.twig # Frontend selection interface │ └── product-selector.twig # Frontend selection interface
├── vendor/ # Composer dependencies (gitignored, included in releases) ├── vendor/ # Composer dependencies (gitignored, included in releases)
├── composer.json ├── composer.json
├── phpcs.xml.dist # PHPCS configuration (WordPress-Extra + PHPCompatibilityWP)
├── phpunit.xml.dist # PHPUnit configuration ├── phpunit.xml.dist # PHPUnit configuration
└── wc-composable-product.php # Main plugin file └── wc-composable-product.php # Main plugin file
``` ```
@@ -140,11 +142,15 @@ Compile .po to .mo: `for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"
WordPress requires compiled .mo files — .po files alone are insufficient. WordPress requires compiled .mo files — .po files alone are insufficient.
## Testing ## Testing & Linting
Run unit tests: `vendor/bin/phpunit --testdox` Run unit tests: `vendor/bin/phpunit --testdox`
Tests use **Brain Monkey** to mock WordPress/WooCommerce functions without a full WP installation. WooCommerce classes (`WC_Product`, `WC_Settings_Page`, etc.) are provided as minimal stubs in `tests/stubs/` so PHP can parse `extends` declarations. The release workflow runs tests before building — a failing test blocks the release. Run coding standards check: `vendor/bin/phpcs`
Auto-fix coding standard violations: `vendor/bin/phpcbf`
Tests use **Brain Monkey** to mock WordPress/WooCommerce functions without a full WP installation. WooCommerce classes (`WC_Product`, `WC_Settings_Page`, etc.) are provided as minimal stubs in `tests/stubs/` so PHP can parse `extends` declarations. PHPCS uses the **WordPress-Extra** standard plus **PHPCompatibilityWP** for PHP version checks. The release workflow runs lint, phpcs, and tests before building — any failure blocks the release.
## Release Workflow ## Release Workflow
+82
View File
@@ -240,3 +240,85 @@
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
/* =========================================================================
Composable product page layout
========================================================================= */
/* Compact product header */
.composable-product-header {
display: flex;
align-items: flex-start;
gap: 1.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.composable-header-thumbnail {
flex-shrink: 0;
}
.composable-header-thumbnail img {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 6px;
}
.composable-header-info {
flex: 1;
min-width: 0;
}
.composable-product-layout .product_title {
margin: 0 0 0.5rem;
font-size: 1.5rem;
line-height: 1.3;
}
.composable-header-price {
margin-bottom: 0.5rem;
font-size: 1.1rem;
color: #2c3e50;
font-weight: 600;
}
.composable-header-description {
color: #666;
font-size: 0.95rem;
line-height: 1.5;
}
.composable-header-description p:last-child {
margin-bottom: 0;
}
/* Full-width selector area */
.composable-selector-area {
margin-bottom: 2rem;
}
/* Override WooCommerce default product layout for composable products */
.single-product-composable .composable-product-layout {
max-width: 100%;
}
.single-product-composable .composable-product-layout .wc-composable-product-selector {
max-width: 100%;
}
/* Responsive: stack header vertically on small screens */
@media (max-width: 768px) {
.composable-product-header {
flex-direction: column;
align-items: center;
text-align: center;
}
.composable-product-layout .product_title {
font-size: 1.25rem;
}
}
+12 -2
View File
@@ -20,8 +20,12 @@
}, },
"require-dev": { "require-dev": {
"brain/monkey": "^2.7", "brain/monkey": "^2.7",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"mockery/mockery": "^1.6", "mockery/mockery": "^1.6",
"phpunit/phpunit": "^9.6" "phpcompatibility/phpcompatibility-wp": "*",
"phpunit/phpunit": "^10.0",
"squizlabs/php_codesniffer": "^3.7",
"wp-coding-standards/wpcs": "^3.0"
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
@@ -30,6 +34,12 @@
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,
"sort-packages": true "sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"platform": {
"php": "8.3.0"
}
} }
} }
Generated
+833 -328
View File
File diff suppressed because it is too large. Load diff
+121 -93
View File
@@ -7,7 +7,7 @@
namespace Magdev\WcComposableProduct\Admin; namespace Magdev\WcComposableProduct\Admin;
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Product Data Tab Class * Product Data Tab Class
@@ -17,10 +17,10 @@ class ProductData {
* Constructor * Constructor
*/ */
public function __construct() { public function __construct() {
add_filter('woocommerce_product_data_tabs', [$this, 'add_product_data_tab']); add_filter( 'woocommerce_product_data_tabs', array( $this, 'add_product_data_tab' ) );
add_action('woocommerce_product_data_panels', [$this, 'add_product_data_panel']); add_action( 'woocommerce_product_data_panels', array( $this, 'add_product_data_panel' ) );
add_action('woocommerce_process_product_meta_composable', [$this, 'save_product_data']); add_action( 'woocommerce_process_product_meta_composable', array( $this, 'save_product_data' ) );
add_action('woocommerce_product_options_general_product_data', [$this, 'add_general_fields']); add_action( 'woocommerce_product_options_general_product_data', array( $this, 'add_general_fields' ) );
} }
/** /**
@@ -29,13 +29,13 @@ class ProductData {
* @param array $tabs Product data tabs * @param array $tabs Product data tabs
* @return array * @return array
*/ */
public function add_product_data_tab($tabs) { public function add_product_data_tab( $tabs ) {
$tabs['composable'] = [ $tabs['composable'] = array(
'label' => __('Composable Options', 'wc-composable-product'), 'label' => __( 'Composable Options', 'wc-composable-product' ),
'target' => 'composable_product_data', 'target' => 'composable_product_data',
'class' => ['show_if_composable'], 'class' => array( 'show_if_composable' ),
'priority' => 21, 'priority' => 21,
]; );
return $tabs; return $tabs;
} }
@@ -45,42 +45,48 @@ class ProductData {
public function add_general_fields() { public function add_general_fields() {
global $product_object; global $product_object;
if ($product_object && $product_object->get_type() === 'composable') { if ( $product_object && $product_object->get_type() === 'composable' ) {
echo '<div class="options_group show_if_composable">'; echo '<div class="options_group show_if_composable">';
woocommerce_wp_text_input([ woocommerce_wp_text_input(
array(
'id' => '_composable_selection_limit', 'id' => '_composable_selection_limit',
'label' => __('Selection Limit', 'wc-composable-product'), 'label' => __( 'Selection Limit', 'wc-composable-product' ),
'description' => __('Maximum number of items customers can select. Leave empty to use global default.', 'wc-composable-product'), 'description' => __( 'Maximum number of items customers can select. Leave empty to use global default.', 'wc-composable-product' ),
'desc_tip' => true, 'desc_tip' => true,
'type' => 'number', 'type' => 'number',
'custom_attributes' => [ 'custom_attributes' => array(
'min' => '1', 'min' => '1',
'step' => '1', 'step' => '1',
], ),
]); )
);
woocommerce_wp_select([ woocommerce_wp_select(
array(
'id' => '_composable_pricing_mode', 'id' => '_composable_pricing_mode',
'label' => __('Pricing Mode', 'wc-composable-product'), 'label' => __( 'Pricing Mode', 'wc-composable-product' ),
'description' => __('How to calculate the price.', 'wc-composable-product'), 'description' => __( 'How to calculate the price.', 'wc-composable-product' ),
'desc_tip' => true, 'desc_tip' => true,
'options' => [ 'options' => array(
'' => __('Use global default', 'wc-composable-product'), '' => __( 'Use global default', 'wc-composable-product' ),
'sum' => __('Sum of selected products', 'wc-composable-product'), 'sum' => __( 'Sum of selected products', 'wc-composable-product' ),
'fixed' => __('Fixed price', 'wc-composable-product'), 'fixed' => __( 'Fixed price', 'wc-composable-product' ),
], ),
]); )
);
woocommerce_wp_text_input([ woocommerce_wp_text_input(
array(
'id' => '_regular_price', 'id' => '_regular_price',
'label' => __('Fixed Price', 'wc-composable-product') . ' (' . get_woocommerce_currency_symbol() . ')', 'label' => __( 'Fixed Price', 'wc-composable-product' ) . ' (' . get_woocommerce_currency_symbol() . ')',
'description' => __('Enter the fixed price for this composable product.', 'wc-composable-product'), 'description' => __( 'Enter the fixed price for this composable product.', 'wc-composable-product' ),
'desc_tip' => true, 'desc_tip' => true,
'type' => 'text', 'type' => 'text',
'data_type' => 'price', 'data_type' => 'price',
'wrapper_class' => 'composable_fixed_price_field', 'wrapper_class' => 'composable_fixed_price_field',
]); )
);
echo '</div>'; echo '</div>';
} }
@@ -95,85 +101,103 @@ class ProductData {
<div id="composable_product_data" class="panel woocommerce_options_panel hidden"> <div id="composable_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group"> <div class="options_group">
<?php <?php
woocommerce_wp_select([ woocommerce_wp_select(
array(
'id' => '_composable_include_unpublished', 'id' => '_composable_include_unpublished',
'label' => __('Include Non-Public Products', 'wc-composable-product'), 'label' => __( 'Include Non-Public Products', 'wc-composable-product' ),
'description' => __('Allow draft and private products in the selection. Useful when products should only be sold as part of a composition.', 'wc-composable-product'), 'description' => __( 'Allow draft and private products in the selection. Useful when products should only be sold as part of a composition.', 'wc-composable-product' ),
'desc_tip' => true, 'desc_tip' => true,
'options' => [ 'options' => array(
'' => __('Use global default', 'wc-composable-product'), '' => __( 'Use global default', 'wc-composable-product' ),
'yes' => __('Yes', 'wc-composable-product'), 'yes' => __( 'Yes', 'wc-composable-product' ),
'no' => __('No', 'wc-composable-product'), 'no' => __( 'No', 'wc-composable-product' ),
], ),
'value' => get_post_meta($post->ID, '_composable_include_unpublished', true) ?: '', 'value' => get_post_meta( $post->ID, '_composable_include_unpublished', true ) ? get_post_meta( $post->ID, '_composable_include_unpublished', true ) : '',
]); )
);
woocommerce_wp_select([ woocommerce_wp_select(
array(
'id' => '_composable_criteria_type', 'id' => '_composable_criteria_type',
'label' => __('Selection Criteria', 'wc-composable-product'), 'label' => __( 'Selection Criteria', 'wc-composable-product' ),
'description' => __('How to select available products.', 'wc-composable-product'), 'description' => __( 'How to select available products.', 'wc-composable-product' ),
'desc_tip' => true, 'desc_tip' => true,
'options' => [ 'options' => array(
'category' => __('By Category', 'wc-composable-product'), 'category' => __( 'By Category', 'wc-composable-product' ),
'tag' => __('By Tag', 'wc-composable-product'), 'tag' => __( 'By Tag', 'wc-composable-product' ),
'sku' => __('By SKU', 'wc-composable-product'), 'sku' => __( 'By SKU', 'wc-composable-product' ),
], ),
'value' => get_post_meta($post->ID, '_composable_criteria_type', true) ?: 'category', 'value' => get_post_meta( $post->ID, '_composable_criteria_type', true ) ? get_post_meta( $post->ID, '_composable_criteria_type', true ) : 'category',
]); )
);
?> ?>
</div> </div>
<div class="options_group composable_criteria_group" id="composable_criteria_category"> <div class="options_group composable_criteria_group" id="composable_criteria_category">
<p class="form-field"> <p class="form-field">
<label for="_composable_categories"><?php esc_html_e('Select Categories', 'wc-composable-product'); ?></label> <label for="_composable_categories"><?php esc_html_e( 'Select Categories', 'wc-composable-product' ); ?></label>
<select id="_composable_categories" name="_composable_categories[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;"> <select id="_composable_categories" name="_composable_categories[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;">
<?php <?php
$selected_categories = get_post_meta($post->ID, '_composable_categories', true) ?: []; $selected_categories = get_post_meta( $post->ID, '_composable_categories', true );
$categories = get_terms(['taxonomy' => 'product_cat', 'hide_empty' => false]); $selected_categories = $selected_categories ? $selected_categories : array();
foreach ($categories as $category) { $categories = get_terms(
array(
'taxonomy' => 'product_cat',
'hide_empty' => false,
)
);
foreach ( $categories as $category ) {
printf( printf(
'<option value="%s" %s>%s</option>', '<option value="%s" %s>%s</option>',
esc_attr($category->term_id), esc_attr( $category->term_id ),
selected(in_array($category->term_id, (array) $selected_categories), true, false), selected( in_array( $category->term_id, (array) $selected_categories, true ), true, false ),
esc_html($category->name) esc_html( $category->name )
); );
} }
?> ?>
</select> </select>
<span class="description"><?php esc_html_e('Select product categories to include.', 'wc-composable-product'); ?></span> <span class="description"><?php esc_html_e( 'Select product categories to include.', 'wc-composable-product' ); ?></span>
</p> </p>
</div> </div>
<div class="options_group composable_criteria_group" id="composable_criteria_tag" style="display: none;"> <div class="options_group composable_criteria_group" id="composable_criteria_tag" style="display: none;">
<p class="form-field"> <p class="form-field">
<label for="_composable_tags"><?php esc_html_e('Select Tags', 'wc-composable-product'); ?></label> <label for="_composable_tags"><?php esc_html_e( 'Select Tags', 'wc-composable-product' ); ?></label>
<select id="_composable_tags" name="_composable_tags[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;"> <select id="_composable_tags" name="_composable_tags[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;">
<?php <?php
$selected_tags = get_post_meta($post->ID, '_composable_tags', true) ?: []; $selected_tags = get_post_meta( $post->ID, '_composable_tags', true );
$tags = get_terms(['taxonomy' => 'product_tag', 'hide_empty' => false]); $selected_tags = $selected_tags ? $selected_tags : array();
foreach ($tags as $tag) { $tags = get_terms(
array(
'taxonomy' => 'product_tag',
'hide_empty' => false,
)
);
foreach ( $tags as $tag ) {
printf( printf(
'<option value="%s" %s>%s</option>', '<option value="%s" %s>%s</option>',
esc_attr($tag->term_id), esc_attr( $tag->term_id ),
selected(in_array($tag->term_id, (array) $selected_tags), true, false), selected( in_array( $tag->term_id, (array) $selected_tags, true ), true, false ),
esc_html($tag->name) esc_html( $tag->name )
); );
} }
?> ?>
</select> </select>
<span class="description"><?php esc_html_e('Select product tags to include.', 'wc-composable-product'); ?></span> <span class="description"><?php esc_html_e( 'Select product tags to include.', 'wc-composable-product' ); ?></span>
</p> </p>
</div> </div>
<div class="options_group composable_criteria_group" id="composable_criteria_sku" style="display: none;"> <div class="options_group composable_criteria_group" id="composable_criteria_sku" style="display: none;">
<?php <?php
woocommerce_wp_textarea_input([ woocommerce_wp_textarea_input(
array(
'id' => '_composable_skus', 'id' => '_composable_skus',
'label' => __('Product SKUs', 'wc-composable-product'), 'label' => __( 'Product SKUs', 'wc-composable-product' ),
'description' => __('Enter product SKUs separated by commas.', 'wc-composable-product'), 'description' => __( 'Enter product SKUs separated by commas.', 'wc-composable-product' ),
'desc_tip' => true, 'desc_tip' => true,
'placeholder' => __('SKU-1, SKU-2, SKU-3', 'wc-composable-product'), 'placeholder' => __( 'SKU-1, SKU-2, SKU-3', 'wc-composable-product' ),
]); )
);
?> ?>
</div> </div>
</div> </div>
@@ -185,33 +209,37 @@ class ProductData {
* *
* @param int $post_id Post ID * @param int $post_id Post ID
*/ */
public function save_product_data($post_id) { public function save_product_data( $post_id ) {
// Save selection limit // phpcs:disable WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce in woocommerce_process_product_meta.
$selection_limit = isset($_POST['_composable_selection_limit']) ? absint($_POST['_composable_selection_limit']) : '';
update_post_meta($post_id, '_composable_selection_limit', $selection_limit);
// Save pricing mode // Save selection limit.
$pricing_mode = isset($_POST['_composable_pricing_mode']) ? sanitize_text_field($_POST['_composable_pricing_mode']) : ''; $selection_limit = isset( $_POST['_composable_selection_limit'] ) ? absint( $_POST['_composable_selection_limit'] ) : '';
update_post_meta($post_id, '_composable_pricing_mode', $pricing_mode); update_post_meta( $post_id, '_composable_selection_limit', $selection_limit );
// Save include unpublished // Save pricing mode.
$include_unpublished = isset($_POST['_composable_include_unpublished']) ? sanitize_text_field($_POST['_composable_include_unpublished']) : ''; $pricing_mode = isset( $_POST['_composable_pricing_mode'] ) ? sanitize_text_field( $_POST['_composable_pricing_mode'] ) : '';
update_post_meta($post_id, '_composable_include_unpublished', $include_unpublished); update_post_meta( $post_id, '_composable_pricing_mode', $pricing_mode );
// Save criteria type // Save include unpublished.
$criteria_type = isset($_POST['_composable_criteria_type']) ? sanitize_text_field($_POST['_composable_criteria_type']) : 'category'; $include_unpublished = isset( $_POST['_composable_include_unpublished'] ) ? sanitize_text_field( $_POST['_composable_include_unpublished'] ) : '';
update_post_meta($post_id, '_composable_criteria_type', $criteria_type); update_post_meta( $post_id, '_composable_include_unpublished', $include_unpublished );
// Save categories // Save criteria type.
$categories = isset($_POST['_composable_categories']) ? array_map('absint', $_POST['_composable_categories']) : []; $criteria_type = isset( $_POST['_composable_criteria_type'] ) ? sanitize_text_field( $_POST['_composable_criteria_type'] ) : 'category';
update_post_meta($post_id, '_composable_categories', $categories); update_post_meta( $post_id, '_composable_criteria_type', $criteria_type );
// Save tags // Save categories.
$tags = isset($_POST['_composable_tags']) ? array_map('absint', $_POST['_composable_tags']) : []; $categories = isset( $_POST['_composable_categories'] ) ? array_map( 'absint', $_POST['_composable_categories'] ) : array();
update_post_meta($post_id, '_composable_tags', $tags); update_post_meta( $post_id, '_composable_categories', $categories );
// Save SKUs // Save tags.
$skus = isset($_POST['_composable_skus']) ? sanitize_textarea_field($_POST['_composable_skus']) : ''; $tags = isset( $_POST['_composable_tags'] ) ? array_map( 'absint', $_POST['_composable_tags'] ) : array();
update_post_meta($post_id, '_composable_skus', $skus); update_post_meta( $post_id, '_composable_tags', $tags );
// Save SKUs.
$skus = isset( $_POST['_composable_skus'] ) ? sanitize_textarea_field( $_POST['_composable_skus'] ) : '';
update_post_meta( $post_id, '_composable_skus', $skus );
// phpcs:enable WordPress.Security.NonceVerification.Missing
} }
} }
+43 -43
View File
@@ -7,7 +7,7 @@
namespace Magdev\WcComposableProduct\Admin; namespace Magdev\WcComposableProduct\Admin;
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Settings class * Settings class
@@ -18,7 +18,7 @@ class Settings extends \WC_Settings_Page {
*/ */
public function __construct() { public function __construct() {
$this->id = 'composable_products'; $this->id = 'composable_products';
$this->label = __('Composable Products', 'wc-composable-product'); $this->label = __( 'Composable Products', 'wc-composable-product' );
parent::__construct(); parent::__construct();
} }
@@ -29,72 +29,72 @@ class Settings extends \WC_Settings_Page {
* @return array * @return array
*/ */
public function get_settings() { public function get_settings() {
$settings = [ $settings = array(
[ array(
'title' => __('Composable Products Settings', 'wc-composable-product'), 'title' => __( 'Composable Products Settings', 'wc-composable-product' ),
'type' => 'title', 'type' => 'title',
'desc' => __('Configure default settings for composable products.', 'wc-composable-product'), 'desc' => __( 'Configure default settings for composable products.', 'wc-composable-product' ),
'id' => 'wc_composable_settings', 'id' => 'wc_composable_settings',
], ),
[ array(
'title' => __('Default Selection Limit', 'wc-composable-product'), 'title' => __( 'Default Selection Limit', 'wc-composable-product' ),
'desc' => __('Default number of items customers can select.', 'wc-composable-product'), 'desc' => __( 'Default number of items customers can select.', 'wc-composable-product' ),
'id' => 'wc_composable_default_limit', 'id' => 'wc_composable_default_limit',
'type' => 'number', 'type' => 'number',
'default' => '5', 'default' => '5',
'custom_attributes' => [ 'custom_attributes' => array(
'min' => '1', 'min' => '1',
'step' => '1', 'step' => '1',
], ),
'desc_tip' => true, 'desc_tip' => true,
], ),
[ array(
'title' => __('Default Pricing Mode', 'wc-composable-product'), 'title' => __( 'Default Pricing Mode', 'wc-composable-product' ),
'desc' => __('How to calculate the price of composable products.', 'wc-composable-product'), 'desc' => __( 'How to calculate the price of composable products.', 'wc-composable-product' ),
'id' => 'wc_composable_default_pricing', 'id' => 'wc_composable_default_pricing',
'type' => 'select', 'type' => 'select',
'default' => 'sum', 'default' => 'sum',
'options' => [ 'options' => array(
'sum' => __('Sum of selected products', 'wc-composable-product'), 'sum' => __( 'Sum of selected products', 'wc-composable-product' ),
'fixed' => __('Fixed price', 'wc-composable-product'), 'fixed' => __( 'Fixed price', 'wc-composable-product' ),
], ),
'desc_tip' => true, 'desc_tip' => true,
], ),
[ array(
'title' => __('Include Non-Public Products', 'wc-composable-product'), 'title' => __( 'Include Non-Public Products', 'wc-composable-product' ),
'desc' => __('Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually.', 'wc-composable-product'), 'desc' => __( 'Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually.', 'wc-composable-product' ),
'id' => 'wc_composable_include_unpublished', 'id' => 'wc_composable_include_unpublished',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'no', 'default' => 'no',
], ),
[ array(
'title' => __('Show Product Images', 'wc-composable-product'), 'title' => __( 'Show Product Images', 'wc-composable-product' ),
'desc' => __('Display product images in the selection interface.', 'wc-composable-product'), 'desc' => __( 'Display product images in the selection interface.', 'wc-composable-product' ),
'id' => 'wc_composable_show_images', 'id' => 'wc_composable_show_images',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'yes', 'default' => 'yes',
], ),
[ array(
'title' => __('Show Product Prices', 'wc-composable-product'), 'title' => __( 'Show Product Prices', 'wc-composable-product' ),
'desc' => __('Display individual product prices in the selection interface.', 'wc-composable-product'), 'desc' => __( 'Display individual product prices in the selection interface.', 'wc-composable-product' ),
'id' => 'wc_composable_show_prices', 'id' => 'wc_composable_show_prices',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'yes', 'default' => 'yes',
], ),
[ array(
'title' => __('Show Total Price', 'wc-composable-product'), 'title' => __( 'Show Total Price', 'wc-composable-product' ),
'desc' => __('Display the total price as customers make selections.', 'wc-composable-product'), 'desc' => __( 'Display the total price as customers make selections.', 'wc-composable-product' ),
'id' => 'wc_composable_show_total', 'id' => 'wc_composable_show_total',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'yes', 'default' => 'yes',
], ),
[ array(
'type' => 'sectionend', 'type' => 'sectionend',
'id' => 'wc_composable_settings', 'id' => 'wc_composable_settings',
], ),
]; );
return apply_filters('wc_composable_settings', $settings); return apply_filters( 'wc_composable_settings', $settings );
} }
/** /**
@@ -102,7 +102,7 @@ class Settings extends \WC_Settings_Page {
*/ */
public function output() { public function output() {
$settings = $this->get_settings(); $settings = $this->get_settings();
\WC_Admin_Settings::output_fields($settings); \WC_Admin_Settings::output_fields( $settings );
} }
/** /**
@@ -110,6 +110,6 @@ class Settings extends \WC_Settings_Page {
*/ */
public function save() { public function save() {
$settings = $this->get_settings(); $settings = $this->get_settings();
\WC_Admin_Settings::save_fields($settings); \WC_Admin_Settings::save_fields( $settings );
} }
} }
+67 -60
View File
@@ -7,7 +7,7 @@
namespace Magdev\WcComposableProduct; namespace Magdev\WcComposableProduct;
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Cart Handler Class * Cart Handler Class
@@ -28,14 +28,14 @@ class CartHandler {
public function __construct() { public function __construct() {
$this->stock_manager = new StockManager(); $this->stock_manager = new StockManager();
add_filter('woocommerce_add_to_cart_validation', [$this, 'validate_add_to_cart'], 10, 3); add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'validate_add_to_cart' ), 10, 3 );
add_filter('woocommerce_add_cart_item_data', [$this, 'add_cart_item_data'], 10, 2); add_filter( 'woocommerce_add_cart_item_data', array( $this, 'add_cart_item_data' ), 10, 2 );
add_filter('woocommerce_get_cart_item_from_session', [$this, 'get_cart_item_from_session'], 10, 2); add_filter( 'woocommerce_get_cart_item_from_session', array( $this, 'get_cart_item_from_session' ), 10, 2 );
add_filter('woocommerce_get_item_data', [$this, 'display_cart_item_data'], 10, 2); add_filter( 'woocommerce_get_item_data', array( $this, 'display_cart_item_data' ), 10, 2 );
add_action('woocommerce_before_calculate_totals', [$this, 'calculate_cart_item_price']); add_action( 'woocommerce_before_calculate_totals', array( $this, 'calculate_cart_item_price' ) );
add_action('woocommerce_single_product_summary', [$this, 'render_product_selector'], 25); add_action( 'woocommerce_single_product_summary', array( $this, 'render_product_selector' ), 25 );
add_action('woocommerce_checkout_create_order_line_item', [$this->stock_manager, 'store_selected_products_in_order'], 10, 3); add_action( 'woocommerce_checkout_create_order_line_item', array( $this->stock_manager, 'store_selected_products_in_order' ), 10, 3 );
add_filter('woocommerce_is_purchasable', [$this, 'hide_default_add_to_cart'], 10, 2); add_filter( 'woocommerce_is_purchasable', array( $this, 'hide_default_add_to_cart' ), 10, 2 );
} }
/** /**
@@ -45,8 +45,8 @@ class CartHandler {
* @param \WC_Product $product Product object * @param \WC_Product $product Product object
* @return bool * @return bool
*/ */
public function hide_default_add_to_cart($is_purchasable, $product) { public function hide_default_add_to_cart( $is_purchasable, $product ) {
if ($product && $product->get_type() === 'composable') { if ( $product && $product->get_type() === 'composable' ) {
return false; return false;
} }
return $is_purchasable; return $is_purchasable;
@@ -58,8 +58,8 @@ class CartHandler {
public function render_product_selector() { public function render_product_selector() {
global $product; global $product;
if ($product && $product->get_type() === 'composable') { if ( $product && $product->get_type() === 'composable' ) {
ProductSelector::render($product); ProductSelector::render( $product );
} }
} }
@@ -71,51 +71,56 @@ class CartHandler {
* @param int $quantity Quantity * @param int $quantity Quantity
* @return bool * @return bool
*/ */
public function validate_add_to_cart($passed, $product_id, $quantity) { public function validate_add_to_cart( $passed, $product_id, $quantity ) {
$product = wc_get_product($product_id); $product = wc_get_product( $product_id );
if (!$product || $product->get_type() !== 'composable') { if ( ! $product || $product->get_type() !== 'composable' ) {
return $passed; return $passed;
} }
// Check if selected products are provided // Check if selected products are provided.
if (!isset($_POST['composable_products']) || empty($_POST['composable_products'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
wc_add_notice(__('Please select at least one product.', 'wc-composable-product'), 'error'); if ( ! isset( $_POST['composable_products'] ) || empty( $_POST['composable_products'] ) ) {
wc_add_notice( __( 'Please select at least one product.', 'wc-composable-product' ), 'error' );
return false; return false;
} }
$selected_products = array_map('absint', $_POST['composable_products']); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
$selected_products = array_map( 'absint', $_POST['composable_products'] );
$selection_limit = $product->get_selection_limit(); $selection_limit = $product->get_selection_limit();
// Validate selection limit // Validate selection limit
if (count($selected_products) > $selection_limit) { if ( count( $selected_products ) > $selection_limit ) {
/* translators: %d: selection limit */ /* translators: %d: selection limit */
wc_add_notice(sprintf(__('You can select a maximum of %d products.', 'wc-composable-product'), $selection_limit), 'error'); wc_add_notice( sprintf( __( 'You can select a maximum of %d products.', 'wc-composable-product' ), $selection_limit ), 'error' );
return false; return false;
} }
if (count($selected_products) === 0) { if ( count( $selected_products ) === 0 ) {
wc_add_notice(__('Please select at least one product.', 'wc-composable-product'), 'error'); wc_add_notice( __( 'Please select at least one product.', 'wc-composable-product' ), 'error' );
return false; return false;
} }
// Validate that selected products are valid // Validate that selected products are valid
$available_products = $product->get_available_products(); $available_products = $product->get_available_products();
$available_ids = array_map(function($p) { $available_ids = array_map(
function ( $p ) {
return $p->get_id(); return $p->get_id();
}, $available_products); },
$available_products
);
foreach ($selected_products as $selected_id) { foreach ( $selected_products as $selected_id ) {
if (!in_array($selected_id, $available_ids)) { if ( ! in_array( $selected_id, $available_ids, true ) ) {
wc_add_notice(__('One or more selected products are not available.', 'wc-composable-product'), 'error'); wc_add_notice( __( 'One or more selected products are not available.', 'wc-composable-product' ), 'error' );
return false; return false;
} }
} }
// Validate stock availability // Validate stock availability
$stock_validation = $this->stock_manager->validate_stock_availability($selected_products, $quantity); $stock_validation = $this->stock_manager->validate_stock_availability( $selected_products, $quantity );
if ($stock_validation !== true) { if ( true !== $stock_validation ) {
wc_add_notice($stock_validation, 'error'); wc_add_notice( $stock_validation, 'error' );
return false; return false;
} }
@@ -129,19 +134,21 @@ class CartHandler {
* @param int $product_id Product ID * @param int $product_id Product ID
* @return array * @return array
*/ */
public function add_cart_item_data($cart_item_data, $product_id) { public function add_cart_item_data( $cart_item_data, $product_id ) {
$product = wc_get_product($product_id); $product = wc_get_product( $product_id );
if (!$product || $product->get_type() !== 'composable') { if ( ! $product || $product->get_type() !== 'composable' ) {
return $cart_item_data; return $cart_item_data;
} }
if (isset($_POST['composable_products']) && !empty($_POST['composable_products'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
$selected_products = array_map('absint', $_POST['composable_products']); if ( isset( $_POST['composable_products'] ) && ! empty( $_POST['composable_products'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
$selected_products = array_map( 'absint', $_POST['composable_products'] );
$cart_item_data['composable_products'] = $selected_products; $cart_item_data['composable_products'] = $selected_products;
// Make cart item unique // Make cart item unique.
$cart_item_data['unique_key'] = md5(json_encode($selected_products) . time()); $cart_item_data['unique_key'] = md5( wp_json_encode( $selected_products ) . time() );
} }
return $cart_item_data; return $cart_item_data;
@@ -154,8 +161,8 @@ class CartHandler {
* @param array $values Values from session * @param array $values Values from session
* @return array * @return array
*/ */
public function get_cart_item_from_session($cart_item, $values) { public function get_cart_item_from_session( $cart_item, $values ) {
if (isset($values['composable_products'])) { if ( isset( $values['composable_products'] ) ) {
$cart_item['composable_products'] = $values['composable_products']; $cart_item['composable_products'] = $values['composable_products'];
} }
@@ -169,21 +176,21 @@ class CartHandler {
* @param array $cart_item Cart item * @param array $cart_item Cart item
* @return array * @return array
*/ */
public function display_cart_item_data($item_data, $cart_item) { public function display_cart_item_data( $item_data, $cart_item ) {
if (isset($cart_item['composable_products']) && !empty($cart_item['composable_products'])) { if ( isset( $cart_item['composable_products'] ) && ! empty( $cart_item['composable_products'] ) ) {
$product_names = []; $product_names = array();
foreach ($cart_item['composable_products'] as $product_id) { foreach ( $cart_item['composable_products'] as $product_id ) {
$product = wc_get_product($product_id); $product = wc_get_product( $product_id );
if ($product) { if ( $product ) {
$product_names[] = $product->get_name(); $product_names[] = $product->get_name();
} }
} }
if (!empty($product_names)) { if ( ! empty( $product_names ) ) {
$item_data[] = [ $item_data[] = array(
'key' => __('Selected Products', 'wc-composable-product'), 'key' => __( 'Selected Products', 'wc-composable-product' ),
'value' => implode(', ', $product_names), 'value' => implode( ', ', $product_names ),
]; );
} }
} }
@@ -195,23 +202,23 @@ class CartHandler {
* *
* @param \WC_Cart $cart Cart object * @param \WC_Cart $cart Cart object
*/ */
public function calculate_cart_item_price($cart) { public function calculate_cart_item_price( $cart ) {
if (is_admin() && !defined('DOING_AJAX')) { if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return; return;
} }
// Use static flag to prevent multiple executions within the same request // Use static flag to prevent multiple executions within the same request
static $already_calculated = false; static $already_calculated = false;
if ($already_calculated) { if ( $already_calculated ) {
return; return;
} }
foreach ($cart->get_cart() as $cart_item_key => $cart_item) { foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
if (isset($cart_item['data']) && $cart_item['data']->get_type() === 'composable') { if ( isset( $cart_item['data'] ) && $cart_item['data']->get_type() === 'composable' ) {
if (isset($cart_item['composable_products'])) { if ( isset( $cart_item['composable_products'] ) ) {
$product = $cart_item['data']; $product = $cart_item['data'];
$price = $product->calculate_composed_price($cart_item['composable_products']); $price = $product->calculate_composed_price( $cart_item['composable_products'] );
$cart_item['data']->set_price($price); $cart_item['data']->set_price( $price );
} }
} }
} }
+101 -47
View File
@@ -7,7 +7,7 @@
namespace Magdev\WcComposableProduct; namespace Magdev\WcComposableProduct;
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Main plugin class - Singleton pattern * Main plugin class - Singleton pattern
@@ -35,7 +35,7 @@ class Plugin {
* @return Plugin * @return Plugin
*/ */
public static function instance() { public static function instance() {
if (is_null(self::$instance)) { if ( is_null( self::$instance ) ) {
self::$instance = new self(); self::$instance = new self();
} }
return self::$instance; return self::$instance;
@@ -55,41 +55,54 @@ class Plugin {
*/ */
private function init_hooks() { private function init_hooks() {
// Register product type // Register product type
add_filter('product_type_selector', [$this, 'add_product_type']); add_filter( 'product_type_selector', array( $this, 'add_product_type' ) );
add_filter('woocommerce_product_class', [$this, 'product_class'], 10, 2); add_filter( 'woocommerce_product_class', array( $this, 'product_class' ), 10, 2 );
// Enqueue scripts and styles // Enqueue scripts and styles
add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_scripts']); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
// Admin settings // Admin settings
add_filter('woocommerce_get_settings_pages', [$this, 'add_settings_page']); add_filter( 'woocommerce_get_settings_pages', array( $this, 'add_settings_page' ) );
// Custom page template for composable products
add_filter( 'wc_get_template_part', array( $this, 'override_single_product_template' ), 10, 3 );
add_filter( 'body_class', array( $this, 'add_composable_body_class' ) );
} }
/** /**
* Initialize Twig template engine * Initialize Twig template engine
*/ */
private function init_twig() { private function init_twig() {
$loader = new \Twig\Loader\FilesystemLoader(WC_COMPOSABLE_PRODUCT_PATH . 'templates'); $loader = new \Twig\Loader\FilesystemLoader( WC_COMPOSABLE_PRODUCT_PATH . 'templates' );
$this->twig = new \Twig\Environment($loader, [ $this->twig = new \Twig\Environment(
$loader,
array(
'cache' => WC_COMPOSABLE_PRODUCT_PATH . 'cache', 'cache' => WC_COMPOSABLE_PRODUCT_PATH . 'cache',
'auto_reload' => true, 'auto_reload' => true,
'debug' => defined('WP_DEBUG') && WP_DEBUG, 'debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
]); )
);
// Add WordPress functions to Twig // Add WordPress functions to Twig
$this->twig->addFunction(new \Twig\TwigFunction('__', function($text) { $this->twig->addFunction(
return __($text, 'wc-composable-product'); new \Twig\TwigFunction(
})); '__',
$this->twig->addFunction(new \Twig\TwigFunction('esc_html', 'esc_html')); function ( $text ) {
$this->twig->addFunction(new \Twig\TwigFunction('esc_attr', 'esc_attr')); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- dynamic Twig template strings.
$this->twig->addFunction(new \Twig\TwigFunction('esc_url', 'esc_url')); return __( $text, 'wc-composable-product' );
$this->twig->addFunction(new \Twig\TwigFunction('wc_price', 'wc_price')); }
)
);
$this->twig->addFunction( new \Twig\TwigFunction( 'esc_html', 'esc_html' ) );
$this->twig->addFunction( new \Twig\TwigFunction( 'esc_attr', 'esc_attr' ) );
$this->twig->addFunction( new \Twig\TwigFunction( 'esc_url', 'esc_url' ) );
$this->twig->addFunction( new \Twig\TwigFunction( 'wc_price', 'wc_price' ) );
// Add WordPress escaping functions as Twig filters // Add WordPress escaping functions as Twig filters
$this->twig->addFilter(new \Twig\TwigFilter('esc_html', 'esc_html')); $this->twig->addFilter( new \Twig\TwigFilter( 'esc_html', 'esc_html' ) );
$this->twig->addFilter(new \Twig\TwigFilter('esc_attr', 'esc_attr')); $this->twig->addFilter( new \Twig\TwigFilter( 'esc_attr', 'esc_attr' ) );
$this->twig->addFilter(new \Twig\TwigFilter('esc_url', 'esc_url')); $this->twig->addFilter( new \Twig\TwigFilter( 'esc_url', 'esc_url' ) );
} }
/** /**
@@ -115,8 +128,8 @@ class Plugin {
* @param array $types Product types * @param array $types Product types
* @return array * @return array
*/ */
public function add_product_type($types) { public function add_product_type( $types ) {
$types['composable'] = __('Composable product', 'wc-composable-product'); $types['composable'] = __( 'Composable product', 'wc-composable-product' );
return $types; return $types;
} }
@@ -127,8 +140,8 @@ class Plugin {
* @param string $product_type Product type * @param string $product_type Product type
* @return string * @return string
*/ */
public function product_class($classname, $product_type) { public function product_class( $classname, $product_type ) {
if ($product_type === 'composable') { if ( 'composable' === $product_type ) {
$classname = 'Magdev\WcComposableProduct\ProductType'; $classname = 'Magdev\WcComposableProduct\ProductType';
} }
return $classname; return $classname;
@@ -138,59 +151,63 @@ class Plugin {
* Enqueue frontend scripts and styles * Enqueue frontend scripts and styles
*/ */
public function enqueue_frontend_scripts() { public function enqueue_frontend_scripts() {
if (is_product()) { if ( is_product() ) {
wp_enqueue_style( wp_enqueue_style(
'wc-composable-product', 'wc-composable-product',
WC_COMPOSABLE_PRODUCT_URL . 'assets/css/frontend.css', WC_COMPOSABLE_PRODUCT_URL . 'assets/css/frontend.css',
[], array(),
WC_COMPOSABLE_PRODUCT_VERSION WC_COMPOSABLE_PRODUCT_VERSION
); );
wp_enqueue_script( wp_enqueue_script(
'wc-composable-product', 'wc-composable-product',
WC_COMPOSABLE_PRODUCT_URL . 'assets/js/frontend.js', WC_COMPOSABLE_PRODUCT_URL . 'assets/js/frontend.js',
['jquery'], array( 'jquery' ),
WC_COMPOSABLE_PRODUCT_VERSION, WC_COMPOSABLE_PRODUCT_VERSION,
true true
); );
wp_localize_script('wc-composable-product', 'wcComposableProduct', [ wp_localize_script(
'ajax_url' => admin_url('admin-ajax.php'), 'wc-composable-product',
'nonce' => wp_create_nonce('wc_composable_product_nonce'), 'wcComposableProduct',
'i18n' => [ array(
'select_items' => __('Please select items', 'wc-composable-product'), 'ajax_url' => admin_url( 'admin-ajax.php' ),
'max_items' => __('Maximum items selected', 'wc-composable-product'), 'nonce' => wp_create_nonce( 'wc_composable_product_nonce' ),
'min_items' => __('Please select at least one item', 'wc-composable-product'), 'i18n' => array(
], 'select_items' => __( 'Please select items', 'wc-composable-product' ),
'price_format' => [ 'max_items' => __( 'Maximum items selected', 'wc-composable-product' ),
'min_items' => __( 'Please select at least one item', 'wc-composable-product' ),
),
'price_format' => array(
'currency_symbol' => get_woocommerce_currency_symbol(), 'currency_symbol' => get_woocommerce_currency_symbol(),
'decimal_separator' => wc_get_price_decimal_separator(), 'decimal_separator' => wc_get_price_decimal_separator(),
'thousand_separator' => wc_get_price_thousand_separator(), 'thousand_separator' => wc_get_price_thousand_separator(),
'decimals' => wc_get_price_decimals(), 'decimals' => wc_get_price_decimals(),
'price_format' => get_woocommerce_price_format(), 'price_format' => get_woocommerce_price_format(),
], ),
]); )
);
} }
} }
/** /**
* Enqueue admin scripts and styles * Enqueue admin scripts and styles
*/ */
public function enqueue_admin_scripts($hook) { public function enqueue_admin_scripts( $hook ) {
if ('post.php' === $hook || 'post-new.php' === $hook) { if ( 'post.php' === $hook || 'post-new.php' === $hook ) {
global $post_type; global $post_type;
if ('product' === $post_type) { if ( 'product' === $post_type ) {
wp_enqueue_style( wp_enqueue_style(
'wc-composable-product-admin', 'wc-composable-product-admin',
WC_COMPOSABLE_PRODUCT_URL . 'assets/css/admin.css', WC_COMPOSABLE_PRODUCT_URL . 'assets/css/admin.css',
[], array(),
WC_COMPOSABLE_PRODUCT_VERSION WC_COMPOSABLE_PRODUCT_VERSION
); );
wp_enqueue_script( wp_enqueue_script(
'wc-composable-product-admin', 'wc-composable-product-admin',
WC_COMPOSABLE_PRODUCT_URL . 'assets/js/admin.js', WC_COMPOSABLE_PRODUCT_URL . 'assets/js/admin.js',
['jquery', 'wc-admin-product-meta-boxes'], array( 'jquery', 'wc-admin-product-meta-boxes' ),
WC_COMPOSABLE_PRODUCT_VERSION, WC_COMPOSABLE_PRODUCT_VERSION,
true true
); );
@@ -204,13 +221,50 @@ class Plugin {
* @param array $settings WooCommerce settings pages * @param array $settings WooCommerce settings pages
* @return array * @return array
*/ */
public function add_settings_page($settings) { public function add_settings_page( $settings ) {
// Include Settings.php here, when WC_Settings_Page is guaranteed to be loaded // Include Settings.php here, when WC_Settings_Page is guaranteed to be loaded
require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Admin/Settings.php'; require_once WC_COMPOSABLE_PRODUCT_PATH . 'includes/Admin/Settings.php';
$settings[] = new Admin\Settings(); $settings[] = new Admin\Settings();
return $settings; return $settings;
} }
/**
* Override single product template for composable products
*
* @param string $template Template path
* @param string $slug Template slug
* @param string $name Template name
* @return string
*/
public function override_single_product_template( $template, $slug, $name ) {
if ( 'content' === $slug && 'single-product' === $name ) {
global $product;
if ( $product && $product->get_type() === 'composable' ) {
$custom_template = WC_COMPOSABLE_PRODUCT_PATH . 'templates/content-single-product-composable.php';
if ( file_exists( $custom_template ) ) {
return $custom_template;
}
}
}
return $template;
}
/**
* Add body class for composable product pages
*
* @param array $classes Body CSS classes
* @return array
*/
public function add_composable_body_class( $classes ) {
if ( is_product() ) {
global $product;
if ( $product && $product->get_type() === 'composable' ) {
$classes[] = 'single-product-composable';
}
}
return $classes;
}
/** /**
* Get Twig environment * Get Twig environment
* *
@@ -227,7 +281,7 @@ class Plugin {
* @param array $context Template variables * @param array $context Template variables
* @return string * @return string
*/ */
public function render_template($template, $context = []) { public function render_template( $template, $context = array() ) {
return $this->twig->render($template, $context); return $this->twig->render( $template, $context );
} }
} }
+19 -18
View File
@@ -7,7 +7,7 @@
namespace Magdev\WcComposableProduct; namespace Magdev\WcComposableProduct;
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Product Selector Class * Product Selector Class
@@ -20,8 +20,8 @@ class ProductSelector {
* *
* @param ProductType $product Composable product * @param ProductType $product Composable product
*/ */
public static function render($product) { public static function render( $product ) {
if (!$product || $product->get_type() !== 'composable') { if ( ! $product || $product->get_type() !== 'composable' ) {
return; return;
} }
@@ -29,34 +29,34 @@ class ProductSelector {
$selection_limit = $product->get_selection_limit(); $selection_limit = $product->get_selection_limit();
$pricing_mode = $product->get_pricing_mode(); $pricing_mode = $product->get_pricing_mode();
$show_images = get_option('wc_composable_show_images', 'yes') === 'yes'; $show_images = get_option( 'wc_composable_show_images', 'yes' ) === 'yes';
$show_prices = get_option('wc_composable_show_prices', 'yes') === 'yes'; $show_prices = get_option( 'wc_composable_show_prices', 'yes' ) === 'yes';
$show_total = get_option('wc_composable_show_total', 'yes') === 'yes'; $show_total = get_option( 'wc_composable_show_total', 'yes' ) === 'yes';
// Get stock manager for stock information // Get stock manager for stock information
$stock_manager = new StockManager(); $stock_manager = new StockManager();
// Prepare product data for template // Prepare product data for template
$products_data = []; $products_data = array();
foreach ($available_products as $available_product) { foreach ( $available_products as $available_product ) {
$stock_info = $stock_manager->get_product_stock_info($available_product->get_id()); $stock_info = $stock_manager->get_product_stock_info( $available_product->get_id() );
$products_data[] = [ $products_data[] = array(
'id' => $available_product->get_id(), 'id' => $available_product->get_id(),
'name' => $available_product->get_name(), 'name' => $available_product->get_name(),
'price' => $available_product->get_price(), 'price' => $available_product->get_price(),
'price_html' => $available_product->get_price_html(), 'price_html' => $available_product->get_price_html(),
'image_url' => wp_get_attachment_image_url($available_product->get_image_id(), 'thumbnail'), 'image_url' => wp_get_attachment_image_url( $available_product->get_image_id(), 'thumbnail' ),
'permalink' => $available_product->get_permalink(), 'permalink' => $available_product->get_permalink(),
'stock_status' => $stock_info['stock_status'], 'stock_status' => $stock_info['stock_status'],
'in_stock' => $stock_info['in_stock'], 'in_stock' => $stock_info['in_stock'],
'stock_quantity' => $stock_info['stock_quantity'], 'stock_quantity' => $stock_info['stock_quantity'],
'managing_stock' => $stock_info['managing_stock'], 'managing_stock' => $stock_info['managing_stock'],
'backorders_allowed' => $stock_info['backorders_allowed'], 'backorders_allowed' => $stock_info['backorders_allowed'],
]; );
} }
$context = [ $context = array(
'product_id' => $product->get_id(), 'product_id' => $product->get_id(),
'products' => $products_data, 'products' => $products_data,
'selection_limit' => $selection_limit, 'selection_limit' => $selection_limit,
@@ -65,13 +65,14 @@ class ProductSelector {
'show_prices' => $show_prices, 'show_prices' => $show_prices,
'show_total' => $show_total, 'show_total' => $show_total,
'fixed_price' => $product->get_price(), 'fixed_price' => $product->get_price(),
'fixed_price_html' => wc_price($product->get_price()), 'fixed_price_html' => wc_price( $product->get_price() ),
'zero_price_html' => wc_price(0), 'zero_price_html' => wc_price( 0 ),
'currency_symbol' => get_woocommerce_currency_symbol(), 'currency_symbol' => get_woocommerce_currency_symbol(),
]; );
// Render template // Render template — Twig handles escaping via registered esc_html/esc_attr/esc_url functions.
$plugin = Plugin::instance(); $plugin = Plugin::instance();
echo $plugin->render_template('product-selector.twig', $context); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped by Twig template.
echo $plugin->render_template( 'product-selector.html.twig', $context );
} }
} }
+65 -60
View File
@@ -7,7 +7,7 @@
namespace Magdev\WcComposableProduct; namespace Magdev\WcComposableProduct;
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Composable Product Type Class * Composable Product Type Class
@@ -25,9 +25,9 @@ class ProductType extends \WC_Product {
* *
* @param mixed $product Product ID or object * @param mixed $product Product ID or object
*/ */
public function __construct($product = 0) { public function __construct( $product = 0 ) {
$this->supports[] = 'ajax_add_to_cart'; $this->supports[] = 'ajax_add_to_cart';
parent::__construct($product); parent::__construct( $product );
} }
/** /**
@@ -45,11 +45,11 @@ class ProductType extends \WC_Product {
* @return int * @return int
*/ */
public function get_selection_limit() { public function get_selection_limit() {
$limit = $this->get_meta('_composable_selection_limit', true); $limit = $this->get_meta( '_composable_selection_limit', true );
if (empty($limit)) { if ( empty( $limit ) ) {
$limit = get_option('wc_composable_default_limit', 5); $limit = get_option( 'wc_composable_default_limit', 5 );
} }
return absint($limit); return absint( $limit );
} }
/** /**
@@ -58,9 +58,9 @@ class ProductType extends \WC_Product {
* @return string 'fixed' or 'sum' * @return string 'fixed' or 'sum'
*/ */
public function get_pricing_mode() { public function get_pricing_mode() {
$mode = $this->get_meta('_composable_pricing_mode', true); $mode = $this->get_meta( '_composable_pricing_mode', true );
if (empty($mode)) { if ( empty( $mode ) ) {
$mode = get_option('wc_composable_default_pricing', 'sum'); $mode = get_option( 'wc_composable_default_pricing', 'sum' );
} }
return $mode; return $mode;
} }
@@ -71,12 +71,17 @@ class ProductType extends \WC_Product {
* @return array * @return array
*/ */
public function get_selection_criteria() { public function get_selection_criteria() {
return [ $type = $this->get_meta( '_composable_criteria_type', true );
'type' => $this->get_meta('_composable_criteria_type', true) ?: 'category', $categories = $this->get_meta( '_composable_categories', true );
'categories' => $this->get_meta('_composable_categories', true) ?: [], $tags = $this->get_meta( '_composable_tags', true );
'tags' => $this->get_meta('_composable_tags', true) ?: [], $skus = $this->get_meta( '_composable_skus', true );
'skus' => $this->get_meta('_composable_skus', true) ?: '',
]; return array(
'type' => $type ? $type : 'category',
'categories' => $categories ? $categories : array(),
'tags' => $tags ? $tags : array(),
'skus' => $skus ? $skus : '',
);
} }
/** /**
@@ -103,14 +108,14 @@ class ProductType extends \WC_Product {
* @return bool * @return bool
*/ */
public function should_include_unpublished() { public function should_include_unpublished() {
$per_product = $this->get_meta('_composable_include_unpublished', true); $per_product = $this->get_meta( '_composable_include_unpublished', true );
if ($per_product === 'yes') { if ( 'yes' === $per_product ) {
return true; return true;
} }
if ($per_product === 'no') { if ( 'no' === $per_product ) {
return false; return false;
} }
return get_option('wc_composable_include_unpublished', 'no') === 'yes'; return 'yes' === get_option( 'wc_composable_include_unpublished', 'no' );
} }
/** /**
@@ -121,84 +126,84 @@ class ProductType extends \WC_Product {
public function get_available_products() { public function get_available_products() {
$criteria = $this->get_selection_criteria(); $criteria = $this->get_selection_criteria();
$include_unpublished = $this->should_include_unpublished(); $include_unpublished = $this->should_include_unpublished();
$args = [ $args = array(
'post_type' => 'product', 'post_type' => 'product',
'posts_per_page' => -1, 'posts_per_page' => -1,
'post_status' => $include_unpublished ? ['publish', 'draft', 'private'] : 'publish', 'post_status' => $include_unpublished ? array( 'publish', 'draft', 'private' ) : 'publish',
'orderby' => 'title', 'orderby' => 'title',
'order' => 'ASC', 'order' => 'ASC',
]; );
// Exclude composable products using the product_type taxonomy // Exclude composable products using the product_type taxonomy
// (WooCommerce stores product types as taxonomy terms, NOT as postmeta) // (WooCommerce stores product types as taxonomy terms, NOT as postmeta)
$args['tax_query'] = [ $args['tax_query'] = array(
'relation' => 'AND', 'relation' => 'AND',
[ array(
'taxonomy' => 'product_type', 'taxonomy' => 'product_type',
'field' => 'slug', 'field' => 'slug',
'terms' => ['composable'], 'terms' => array( 'composable' ),
'operator' => 'NOT IN', 'operator' => 'NOT IN',
], ),
]; );
switch ($criteria['type']) { switch ( $criteria['type'] ) {
case 'category': case 'category':
if (!empty($criteria['categories'])) { if ( ! empty( $criteria['categories'] ) ) {
$args['tax_query'][] = [ $args['tax_query'][] = array(
'taxonomy' => 'product_cat', 'taxonomy' => 'product_cat',
'field' => 'term_id', 'field' => 'term_id',
'terms' => $criteria['categories'], 'terms' => $criteria['categories'],
'operator' => 'IN', 'operator' => 'IN',
]; );
} }
break; break;
case 'tag': case 'tag':
if (!empty($criteria['tags'])) { if ( ! empty( $criteria['tags'] ) ) {
$args['tax_query'][] = [ $args['tax_query'][] = array(
'taxonomy' => 'product_tag', 'taxonomy' => 'product_tag',
'field' => 'term_id', 'field' => 'term_id',
'terms' => $criteria['tags'], 'terms' => $criteria['tags'],
'operator' => 'IN', 'operator' => 'IN',
]; );
} }
break; break;
case 'sku': case 'sku':
if (!empty($criteria['skus'])) { if ( ! empty( $criteria['skus'] ) ) {
$skus = array_map('trim', explode(',', $criteria['skus'])); $skus = array_map( 'trim', explode( ',', $criteria['skus'] ) );
$args['meta_query'] = [ $args['meta_query'] = array(
[ array(
'key' => '_sku', 'key' => '_sku',
'value' => $skus, 'value' => $skus,
'compare' => 'IN', 'compare' => 'IN',
], ),
]; );
} }
break; break;
} }
$query = new \WP_Query($args); $query = new \WP_Query( $args );
$products = []; $products = array();
if ($query->have_posts()) { if ( $query->have_posts() ) {
foreach ($query->posts as $post) { foreach ( $query->posts as $post ) {
$product = wc_get_product($post->ID); $product = wc_get_product( $post->ID );
if (!$product) { if ( ! $product ) {
continue; continue;
} }
// Handle variable products by including their variations // Handle variable products by including their variations
if ($product->is_type('variable')) { if ( $product->is_type( 'variable' ) ) {
$variation_ids = $product->get_children(); $variation_ids = $product->get_children();
foreach ($variation_ids as $variation_id) { foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product($variation_id); $variation = wc_get_product( $variation_id );
if ($variation && ($include_unpublished || $variation->is_purchasable())) { if ( $variation && ( $include_unpublished || $variation->is_purchasable() ) ) {
$products[] = $variation; $products[] = $variation;
} }
} }
} elseif ($include_unpublished || $product->is_purchasable()) { } elseif ( $include_unpublished || $product->is_purchasable() ) {
$products[] = $product; $products[] = $product;
} }
} }
@@ -215,18 +220,18 @@ class ProductType extends \WC_Product {
* @param array $selected_products Array of product IDs * @param array $selected_products Array of product IDs
* @return float * @return float
*/ */
public function calculate_composed_price($selected_products) { public function calculate_composed_price( $selected_products ) {
$pricing_mode = $this->get_pricing_mode(); $pricing_mode = $this->get_pricing_mode();
if ($pricing_mode === 'fixed') { if ( 'fixed' === $pricing_mode ) {
return floatval($this->get_regular_price()); return floatval( $this->get_regular_price() );
} }
$total = 0; $total = 0;
foreach ($selected_products as $product_id) { foreach ( $selected_products as $product_id ) {
$product = wc_get_product($product_id); $product = wc_get_product( $product_id );
if ($product) { if ( $product ) {
$total += floatval($product->get_price()); $total += floatval( $product->get_price() );
} }
} }
@@ -243,7 +248,7 @@ class ProductType extends \WC_Product {
* @param array $cart_item_data Cart item data * @param array $cart_item_data Cart item data
* @return bool * @return bool
*/ */
public function add_to_cart_validation($product_id, $quantity, $variation_id = 0, $variations = [], $cart_item_data = []) { public function add_to_cart_validation( $product_id, $quantity, $variation_id = 0, $variations = array(), $cart_item_data = array() ) {
return true; return true;
} }
} }
+60 -60
View File
@@ -7,7 +7,7 @@
namespace Magdev\WcComposableProduct; namespace Magdev\WcComposableProduct;
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
/** /**
* Stock Manager Class * Stock Manager Class
@@ -20,15 +20,15 @@ class StockManager {
*/ */
public function __construct() { public function __construct() {
// Hook into order completion to reduce stock // Hook into order completion to reduce stock
add_action('woocommerce_order_status_completed', [$this, 'reduce_stock_on_order_complete'], 10, 1); add_action( 'woocommerce_order_status_completed', array( $this, 'reduce_stock_on_order_complete' ), 10, 1 );
add_action('woocommerce_order_status_processing', [$this, 'reduce_stock_on_order_complete'], 10, 1); add_action( 'woocommerce_order_status_processing', array( $this, 'reduce_stock_on_order_complete' ), 10, 1 );
// Hook into order cancellation/refund to restore stock // Hook into order cancellation/refund to restore stock
add_action('woocommerce_order_status_cancelled', [$this, 'restore_stock_on_order_cancel'], 10, 1); add_action( 'woocommerce_order_status_cancelled', array( $this, 'restore_stock_on_order_cancel' ), 10, 1 );
add_action('woocommerce_order_status_refunded', [$this, 'restore_stock_on_order_cancel'], 10, 1); add_action( 'woocommerce_order_status_refunded', array( $this, 'restore_stock_on_order_cancel' ), 10, 1 );
// Prevent double stock reduction // Prevent double stock reduction
add_filter('woocommerce_can_reduce_order_stock', [$this, 'prevent_composable_stock_reduction'], 10, 2); add_filter( 'woocommerce_can_reduce_order_stock', array( $this, 'prevent_composable_stock_reduction' ), 10, 2 );
} }
/** /**
@@ -38,42 +38,42 @@ class StockManager {
* @param int $quantity Quantity of composable product being added * @param int $quantity Quantity of composable product being added
* @return bool|string True if in stock, error message otherwise * @return bool|string True if in stock, error message otherwise
*/ */
public function validate_stock_availability($selected_product_ids, $quantity = 1) { public function validate_stock_availability( $selected_product_ids, $quantity = 1 ) {
foreach ($selected_product_ids as $product_id) { foreach ( $selected_product_ids as $product_id ) {
$product = wc_get_product($product_id); $product = wc_get_product( $product_id );
if (!$product) { if ( ! $product ) {
continue; continue;
} }
// Skip stock check if stock management is disabled for this product // Skip stock check if stock management is disabled for this product
if (!$product->managing_stock()) { if ( ! $product->managing_stock() ) {
continue; continue;
} }
$stock_quantity = $product->get_stock_quantity(); $stock_quantity = $product->get_stock_quantity();
// Check if product is in stock // Check if product is in stock
if (!$product->is_in_stock()) { if ( ! $product->is_in_stock() ) {
return sprintf( return sprintf(
/* translators: %s: product name */ /* translators: %s: product name */
__('"%s" is out of stock and cannot be selected.', 'wc-composable-product'), __( '"%s" is out of stock and cannot be selected.', 'wc-composable-product' ),
$product->get_name() $product->get_name()
); );
} }
// Check if enough stock is available // Check if enough stock is available
if ($stock_quantity !== null && $stock_quantity < $quantity) { if ( null !== $stock_quantity && $stock_quantity < $quantity ) {
return sprintf( return sprintf(
/* translators: 1: product name, 2: stock quantity */ /* translators: 1: product name, 2: stock quantity */
__('Only %2$d of "%1$s" are available in stock.', 'wc-composable-product'), __( 'Only %2$d of "%1$s" are available in stock.', 'wc-composable-product' ),
$product->get_name(), $product->get_name(),
$stock_quantity $stock_quantity
); );
} }
// Check for backorders // Check for backorders
if ($product->backorders_allowed()) { if ( $product->backorders_allowed() ) {
continue; continue;
} }
} }
@@ -88,29 +88,29 @@ class StockManager {
* @param int $required_quantity Required quantity * @param int $required_quantity Required quantity
* @return array Stock information [in_stock, stock_quantity, backorders_allowed] * @return array Stock information [in_stock, stock_quantity, backorders_allowed]
*/ */
public function get_product_stock_info($product_id, $required_quantity = 1) { public function get_product_stock_info( $product_id, $required_quantity = 1 ) {
$product = wc_get_product($product_id); $product = wc_get_product( $product_id );
if (!$product) { if ( ! $product ) {
return [ return array(
'in_stock' => false, 'in_stock' => false,
'stock_quantity' => 0, 'stock_quantity' => 0,
'backorders_allowed' => false, 'backorders_allowed' => false,
'stock_status' => 'outofstock', 'stock_status' => 'outofstock',
]; );
} }
$stock_quantity = $product->get_stock_quantity(); $stock_quantity = $product->get_stock_quantity();
$managing_stock = $product->managing_stock(); $managing_stock = $product->managing_stock();
return [ return array(
'in_stock' => $product->is_in_stock(), 'in_stock' => $product->is_in_stock(),
'stock_quantity' => $stock_quantity, 'stock_quantity' => $stock_quantity,
'backorders_allowed' => $product->backorders_allowed(), 'backorders_allowed' => $product->backorders_allowed(),
'stock_status' => $product->get_stock_status(), 'stock_status' => $product->get_stock_status(),
'managing_stock' => $managing_stock, 'managing_stock' => $managing_stock,
'has_enough_stock' => !$managing_stock || $stock_quantity === null || $stock_quantity >= $required_quantity, 'has_enough_stock' => ! $managing_stock || null === $stock_quantity || $stock_quantity >= $required_quantity,
]; );
} }
/** /**
@@ -118,54 +118,54 @@ class StockManager {
* *
* @param int $order_id Order ID * @param int $order_id Order ID
*/ */
public function reduce_stock_on_order_complete($order_id) { public function reduce_stock_on_order_complete( $order_id ) {
$order = wc_get_order($order_id); $order = wc_get_order( $order_id );
if (!$order) { if ( ! $order ) {
return; return;
} }
// Check if stock has already been reduced // Check if stock has already been reduced
if ($order->get_meta('_composable_stock_reduced', true)) { if ( $order->get_meta( '_composable_stock_reduced', true ) ) {
return; return;
} }
foreach ($order->get_items() as $item) { foreach ( $order->get_items() as $item ) {
$product = $item->get_product(); $product = $item->get_product();
if (!$product || $product->get_type() !== 'composable') { if ( ! $product || $product->get_type() !== 'composable' ) {
continue; continue;
} }
// Get selected products from order item meta // Get selected products from order item meta
$selected_products = $item->get_meta('_composable_products', true); $selected_products = $item->get_meta( '_composable_products', true );
if (empty($selected_products) || !is_array($selected_products)) { if ( empty( $selected_products ) || ! is_array( $selected_products ) ) {
continue; continue;
} }
$quantity = $item->get_quantity(); $quantity = $item->get_quantity();
// Reduce stock for each selected product // Reduce stock for each selected product
foreach ($selected_products as $product_id) { foreach ( $selected_products as $product_id ) {
$selected_product = wc_get_product($product_id); $selected_product = wc_get_product( $product_id );
if (!$selected_product || !$selected_product->managing_stock()) { if ( ! $selected_product || ! $selected_product->managing_stock() ) {
continue; continue;
} }
$stock_quantity = $selected_product->get_stock_quantity(); $stock_quantity = $selected_product->get_stock_quantity();
if ($stock_quantity !== null) { if ( null !== $stock_quantity ) {
$new_stock = $stock_quantity - $quantity; $new_stock = $stock_quantity - $quantity;
$selected_product->set_stock_quantity($new_stock); $selected_product->set_stock_quantity( $new_stock );
$selected_product->save(); $selected_product->save();
// Add order note // Add order note
$order->add_order_note( $order->add_order_note(
sprintf( sprintf(
/* translators: 1: product name, 2: quantity, 3: remaining stock */ /* translators: 1: product name, 2: quantity, 3: remaining stock */
__('Stock reduced for "%1$s": -%2$d (remaining: %3$d)', 'wc-composable-product'), __( 'Stock reduced for "%1$s": -%2$d (remaining: %3$d)', 'wc-composable-product' ),
$selected_product->get_name(), $selected_product->get_name(),
$quantity, $quantity,
$new_stock $new_stock
@@ -176,7 +176,7 @@ class StockManager {
} }
// Mark stock as reduced // Mark stock as reduced
$order->update_meta_data('_composable_stock_reduced', true); $order->update_meta_data( '_composable_stock_reduced', true );
$order->save(); $order->save();
} }
@@ -185,54 +185,54 @@ class StockManager {
* *
* @param int $order_id Order ID * @param int $order_id Order ID
*/ */
public function restore_stock_on_order_cancel($order_id) { public function restore_stock_on_order_cancel( $order_id ) {
$order = wc_get_order($order_id); $order = wc_get_order( $order_id );
if (!$order) { if ( ! $order ) {
return; return;
} }
// Check if stock was reduced // Check if stock was reduced
if (!$order->get_meta('_composable_stock_reduced', true)) { if ( ! $order->get_meta( '_composable_stock_reduced', true ) ) {
return; return;
} }
foreach ($order->get_items() as $item) { foreach ( $order->get_items() as $item ) {
$product = $item->get_product(); $product = $item->get_product();
if (!$product || $product->get_type() !== 'composable') { if ( ! $product || $product->get_type() !== 'composable' ) {
continue; continue;
} }
// Get selected products from order item meta // Get selected products from order item meta
$selected_products = $item->get_meta('_composable_products', true); $selected_products = $item->get_meta( '_composable_products', true );
if (empty($selected_products) || !is_array($selected_products)) { if ( empty( $selected_products ) || ! is_array( $selected_products ) ) {
continue; continue;
} }
$quantity = $item->get_quantity(); $quantity = $item->get_quantity();
// Restore stock for each selected product // Restore stock for each selected product
foreach ($selected_products as $product_id) { foreach ( $selected_products as $product_id ) {
$selected_product = wc_get_product($product_id); $selected_product = wc_get_product( $product_id );
if (!$selected_product || !$selected_product->managing_stock()) { if ( ! $selected_product || ! $selected_product->managing_stock() ) {
continue; continue;
} }
$stock_quantity = $selected_product->get_stock_quantity(); $stock_quantity = $selected_product->get_stock_quantity();
if ($stock_quantity !== null) { if ( null !== $stock_quantity ) {
$new_stock = $stock_quantity + $quantity; $new_stock = $stock_quantity + $quantity;
$selected_product->set_stock_quantity($new_stock); $selected_product->set_stock_quantity( $new_stock );
$selected_product->save(); $selected_product->save();
// Add order note // Add order note
$order->add_order_note( $order->add_order_note(
sprintf( sprintf(
/* translators: 1: product name, 2: quantity, 3: new stock */ /* translators: 1: product name, 2: quantity, 3: new stock */
__('Stock restored for "%1$s": +%2$d (total: %3$d)', 'wc-composable-product'), __( 'Stock restored for "%1$s": +%2$d (total: %3$d)', 'wc-composable-product' ),
$selected_product->get_name(), $selected_product->get_name(),
$quantity, $quantity,
$new_stock $new_stock
@@ -243,7 +243,7 @@ class StockManager {
} }
// Mark stock as restored // Mark stock as restored
$order->update_meta_data('_composable_stock_reduced', false); $order->update_meta_data( '_composable_stock_reduced', false );
$order->save(); $order->save();
} }
@@ -255,11 +255,11 @@ class StockManager {
* @param \WC_Order $order Order object * @param \WC_Order $order Order object
* @return bool * @return bool
*/ */
public function prevent_composable_stock_reduction($reduce_stock, $order) { public function prevent_composable_stock_reduction( $reduce_stock, $order ) {
foreach ($order->get_items() as $item) { foreach ( $order->get_items() as $item ) {
$product = $item->get_product(); $product = $item->get_product();
if ($product && $product->get_type() === 'composable') { if ( $product && $product->get_type() === 'composable' ) {
// We'll handle stock reduction manually // We'll handle stock reduction manually
return false; return false;
} }
@@ -275,9 +275,9 @@ class StockManager {
* @param string $cart_item_key Cart item key * @param string $cart_item_key Cart item key
* @param array $values Cart item values * @param array $values Cart item values
*/ */
public function store_selected_products_in_order($item, $cart_item_key, $values) { public function store_selected_products_in_order( $item, $cart_item_key, $values ) {
if (isset($values['composable_products']) && !empty($values['composable_products'])) { if ( isset( $values['composable_products'] ) && ! empty( $values['composable_products'] ) ) {
$item->add_meta_data('_composable_products', $values['composable_products'], true); $item->add_meta_data( '_composable_products', $values['composable_products'], true );
} }
} }
} }
+275 -223
View File
@@ -4,8 +4,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Composable Products 1.0.0\n" "Project-Id-Version: WooCommerce Composable Products 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n" "Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/"
"POT-Creation-Date: 2024-12-31 00:00+0000\n" "issues\n"
"POT-Creation-Date: 2026-03-01 13:47+0100\n"
"PO-Revision-Date: 2024-12-31 00:00+0000\n" "PO-Revision-Date: 2024-12-31 00:00+0000\n"
"Last-Translator: Claude AI\n" "Last-Translator: Claude AI\n"
"Language-Team: German (Switzerland)\n" "Language-Team: German (Switzerland)\n"
@@ -15,250 +16,301 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: wc-composable-product.php #: wc-composable-product.php:44
msgid "WooCommerce Composable Products requires WooCommerce to be installed and active." msgid ""
msgstr "WooCommerce Composable Products erfordert eine installierte und aktive WooCommerce-Installation." "WooCommerce Composable Products requires WooCommerce to be installed and "
"active."
msgstr ""
"WooCommerce Composable Products erfordert eine installierte und aktive "
"WooCommerce-Installation."
#: wc-composable-product.php #: wc-composable-product.php:90
msgid "This plugin requires WooCommerce to be installed and active." msgid "This plugin requires WooCommerce to be installed and active."
msgstr "Dieses Plugin erfordert eine installierte und aktive WooCommerce-Installation." msgstr ""
"Dieses Plugin erfordert eine installierte und aktive WooCommerce-"
"Installation."
#: wc-composable-product.php #: wc-composable-product.php:91
msgid "Plugin Activation Error" msgid "Plugin Activation Error"
msgstr "Plugin-Aktivierungsfehler" msgstr "Plugin-Aktivierungsfehler"
#: includes/Admin/Settings.php #. translators: %s: product name
msgid "Composable Products" #: includes/StockManager.php:60
msgstr "Zusammenstellbare Produkte" #, php-format
#: includes/Admin/Settings.php
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php
msgid "Configure default settings for composable products."
msgstr "Konfigurieren Sie die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php
msgid "Default number of items customers can select."
msgstr "Standardanzahl der Artikel, die Kunden auswählen können (z.B. 5 Sticker für CHF 50.-)."
#: includes/Admin/Settings.php
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php
msgid "How to calculate the price of composable products."
msgstr "Wie der Preis für zusammenstellbare Produkte berechnet wird (z.B. Festpreis CHF 50.- oder Summe)."
#: includes/Admin/Settings.php
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: includes/Admin/Settings.php
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#: includes/Admin/Settings.php
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Plugin.php
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/ProductSelector.php, templates/product-selector.twig
msgid "Select Your Products"
msgstr "Wählen Sie Ihre Produkte"
#: templates/product-selector.twig
msgid "Choose up to"
msgstr "Wählen Sie bis zu"
#: templates/product-selector.twig
msgid "items from the selection below."
msgstr "Artikel aus der untenstehenden Auswahl."
#: templates/product-selector.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: templates/product-selector.twig
msgid "Add to Cart"
msgstr "In den Warenkorb"
#: includes/CartHandler.php
msgid "Please select at least one product."
msgstr "Bitte wählen Sie mindestens ein Produkt aus."
#: includes/CartHandler.php
msgid "You can select a maximum of %d products."
msgstr "Sie können maximal %d Produkte auswählen."
#: includes/CartHandler.php
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: includes/CartHandler.php
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Admin/ProductData.php
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/ProductData.php
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr "Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php
msgid "Enter the fixed price for this composable product."
msgstr "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: includes/Admin/ProductData.php
msgid "By Category"
msgstr "Nach Kategorie"
#: includes/Admin/ProductData.php
msgid "By Tag"
msgstr "Nach Schlagwort"
#: includes/Admin/ProductData.php
msgid "By SKU"
msgstr "Nach Artikelnummer"
#: includes/Admin/ProductData.php
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php
msgid "Enter product SKUs separated by commas."
msgstr "Geben Sie Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: includes/StockManager.php
msgid "\"%s\" is out of stock and cannot be selected." msgid "\"%s\" is out of stock and cannot be selected."
msgstr "\"%s\" ist nicht an Lager und kann nicht ausgewählt werden." msgstr "\"%s\" ist nicht an Lager und kann nicht ausgewählt werden."
#: includes/StockManager.php #: templates/product-selector.html.twig
msgid "Only %2$d of \"%1$s\" are available in stock." msgid "Add to Cart"
msgstr "Nur %2$d von \"%1$s\" sind an Lager verfügbar." msgstr "In den Warenkorb"
#: includes/StockManager.php #: includes/Admin/ProductData.php:108
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)" msgid ""
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)" "Allow draft and private products in the selection. Useful when products "
"should only be sold as part of a composition."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn "
"Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/StockManager.php #: includes/Admin/Settings.php:65
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)" msgid ""
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)" "Allow draft and private products to appear in composable product selections. "
"Useful when products should only be sold as part of a composition, not "
"individually."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte "
"anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung "
"verkauft werden sollen, nicht einzeln."
#: templates/product-selector.twig #: includes/Admin/ProductData.php:126
msgid "Out of stock" msgid "By Category"
msgstr "Nicht an Lager" msgstr "Nach Kategorie"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:128
msgid "Only" msgid "By SKU"
msgstr "Nur" msgstr "Nach Artikelnummer"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:127
msgid "left" msgid "By Tag"
msgstr "übrig" msgstr "Nach Schlagwort"
#: templates/product-selector.twig #: templates/product-selector.html.twig
msgid "Choose up to"
msgstr "Wählen Sie bis zu"
#: includes/Admin/ProductData.php:34
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Plugin.php:132
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/Admin/Settings.php:21
msgid "Composable Products"
msgstr "Zusammenstellbare Produkte"
#: includes/Admin/Settings.php:34
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php:36
msgid "Configure default settings for composable products."
msgstr ""
"Konfigurieren Sie die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php:52
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php:40
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php:41
msgid "Default number of items customers can select."
msgstr ""
"Standardanzahl der Artikel, die Kunden auswählen können (z.B. 5 Sticker für "
"CHF 50.-)."
#: includes/Admin/Settings.php:79
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:72
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:86
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Admin/ProductData.php:196
msgid "Enter product SKUs separated by commas."
msgstr "Geben Sie Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php:83
msgid "Enter the fixed price for this composable product."
msgstr "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php:82
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php:74 includes/Admin/Settings.php:59
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php:53
msgid "How to calculate the price of composable products."
msgstr ""
"Wie der Preis für zusammenstellbare Produkte berechnet wird (z.B. Festpreis "
"CHF 50.- oder Summe)."
#: includes/Admin/ProductData.php:69
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php:123
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: templates/product-selector.html.twig
msgid "In stock" msgid "In stock"
msgstr "An Lager" msgstr "An Lager"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:107 includes/Admin/Settings.php:64
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfigurieren Sie die Produktkriterien im Admin-Bereich."
#: includes/Admin/Settings.php, includes/Admin/ProductData.php
msgid "Include Non-Public Products" msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen" msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php #: templates/product-selector.html.twig
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually." msgid "items from the selection below."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln." msgstr "Artikel aus der untenstehenden Auswahl."
#: includes/Admin/ProductData.php #: includes/Plugin.php:178
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition." msgid "Maximum items selected"
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen." msgstr ""
#: includes/Admin/ProductData.php #: includes/Admin/ProductData.php:55
msgid ""
"Maximum number of items customers can select. Leave empty to use global "
"default."
msgstr ""
"Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um "
"den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php:113
msgid "No"
msgstr "Nein"
#: templates/product-selector.html.twig
msgid ""
"No products available for selection. Please configure the product criteria "
"in the admin panel."
msgstr ""
"Keine Produkte zur Auswahl verfügbar. Bitte konfigurieren Sie die "
"Produktkriterien im Admin-Bereich."
#: includes/CartHandler.php:115
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: templates/product-selector.html.twig
msgid "Only"
msgstr "Nur"
#. translators: 1: product name, 2: stock quantity
#: includes/StockManager.php:69
#, php-format
msgid "Only %2$d of \"%1$s\" are available in stock."
msgstr "Nur %2$d von \"%1$s\" sind an Lager verfügbar."
#: templates/product-selector.html.twig
msgid "Out of stock"
msgstr "Nicht an Lager"
#: includes/CartHandler.php:84 includes/CartHandler.php:100
msgid "Please select at least one product."
msgstr "Bitte wählen Sie mindestens ein Produkt aus."
#: includes/Plugin.php:179
msgid "Please select at least one item"
msgstr ""
#: includes/Plugin.php:177
msgid "Please select items"
msgstr ""
#: includes/Admin/ProductData.php:68
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php:195
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php:198
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: templates/product-selector.html.twig
msgid "Select Your Products"
msgstr "Wählen Sie Ihre Produkte"
#: includes/Admin/ProductData.php:138
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php:165
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php:159
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php:186
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/CartHandler.php:191
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php:122
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php:54
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/Settings.php:71
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php:78
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php:85
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#. translators: 1: product name, 2: quantity, 3: remaining stock
#: includes/StockManager.php:168
#, php-format
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
#. translators: 1: product name, 2: quantity, 3: new stock
#: includes/StockManager.php:235
#, php-format
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
#: includes/Admin/ProductData.php:73 includes/Admin/Settings.php:58
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: templates/product-selector.html.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: includes/Admin/ProductData.php:72 includes/Admin/ProductData.php:111
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php:112
msgid "Yes" msgid "Yes"
msgstr "Ja" msgstr "Ja"
#: includes/Admin/ProductData.php #. translators: %d: selection limit
msgid "No" #: includes/CartHandler.php:95
msgstr "Nein" #, php-format
msgid "You can select a maximum of %d products."
msgstr "Sie können maximal %d Produkte auswählen."
#: templates/product-selector.html.twig
msgid "left"
msgstr "übrig"
+274 -223
View File
@@ -4,8 +4,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Composable Products 1.0.0\n" "Project-Id-Version: WooCommerce Composable Products 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n" "Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/"
"POT-Creation-Date: 2024-12-31 00:00+0000\n" "issues\n"
"POT-Creation-Date: 2026-03-01 13:47+0100\n"
"PO-Revision-Date: 2024-12-31 00:00+0000\n" "PO-Revision-Date: 2024-12-31 00:00+0000\n"
"Last-Translator: Claude AI\n" "Last-Translator: Claude AI\n"
"Language-Team: German (Switzerland)\n" "Language-Team: German (Switzerland)\n"
@@ -15,250 +16,300 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: wc-composable-product.php #: wc-composable-product.php:44
msgid "WooCommerce Composable Products requires WooCommerce to be installed and active." msgid ""
msgstr "WooCommerce Composable Products erfordert eine installierte und aktive WooCommerce-Installation." "WooCommerce Composable Products requires WooCommerce to be installed and "
"active."
msgstr ""
"WooCommerce Composable Products erfordert eine installierte und aktive "
"WooCommerce-Installation."
#: wc-composable-product.php #: wc-composable-product.php:90
msgid "This plugin requires WooCommerce to be installed and active." msgid "This plugin requires WooCommerce to be installed and active."
msgstr "Dieses Plugin erfordert eine installierte und aktive WooCommerce-Installation." msgstr ""
"Dieses Plugin erfordert eine installierte und aktive WooCommerce-"
"Installation."
#: wc-composable-product.php #: wc-composable-product.php:91
msgid "Plugin Activation Error" msgid "Plugin Activation Error"
msgstr "Plugin-Aktivierungsfehler" msgstr "Plugin-Aktivierungsfehler"
#: includes/Admin/Settings.php #. translators: %s: product name
msgid "Composable Products" #: includes/StockManager.php:60
msgstr "Zusammenstellbare Produkte" #, php-format
#: includes/Admin/Settings.php
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php
msgid "Configure default settings for composable products."
msgstr "Konfiguriere die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php
msgid "Default number of items customers can select."
msgstr "Standardanzahl der Artikel, die Kunden auswählen können (z.B. 5 Sticker für CHF 50.-)."
#: includes/Admin/Settings.php
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php
msgid "How to calculate the price of composable products."
msgstr "Wie der Preis für zusammenstellbare Produkte berechnet wird (z.B. Festpreis CHF 50.- oder Summe)."
#: includes/Admin/Settings.php
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: includes/Admin/Settings.php
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#: includes/Admin/Settings.php
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Plugin.php
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/ProductSelector.php, templates/product-selector.twig
msgid "Select Your Products"
msgstr "Wähle deine Produkte"
#: templates/product-selector.twig
msgid "Choose up to"
msgstr "Wähle bis zu"
#: templates/product-selector.twig
msgid "items from the selection below."
msgstr "Artikel aus der untenstehenden Auswahl."
#: templates/product-selector.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: templates/product-selector.twig
msgid "Add to Cart"
msgstr "In den Warenkorb"
#: includes/CartHandler.php
msgid "Please select at least one product."
msgstr "Bitte wähle mindestens ein Produkt aus."
#: includes/CartHandler.php
msgid "You can select a maximum of %d products."
msgstr "Du kannst maximal %d Produkte auswählen."
#: includes/CartHandler.php
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: includes/CartHandler.php
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Admin/ProductData.php
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/ProductData.php
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr "Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php
msgid "Enter the fixed price for this composable product."
msgstr "Gib den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: includes/Admin/ProductData.php
msgid "By Category"
msgstr "Nach Kategorie"
#: includes/Admin/ProductData.php
msgid "By Tag"
msgstr "Nach Schlagwort"
#: includes/Admin/ProductData.php
msgid "By SKU"
msgstr "Nach Artikelnummer"
#: includes/Admin/ProductData.php
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php
msgid "Enter product SKUs separated by commas."
msgstr "Gib Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: includes/StockManager.php
msgid "\"%s\" is out of stock and cannot be selected." msgid "\"%s\" is out of stock and cannot be selected."
msgstr "\"%s\" ist nicht an Lager und kann nicht ausgewählt werden." msgstr "\"%s\" ist nicht an Lager und kann nicht ausgewählt werden."
#: includes/StockManager.php #: templates/product-selector.html.twig
msgid "Only %2$d of \"%1$s\" are available in stock." msgid "Add to Cart"
msgstr "Nur %2$d von \"%1$s\" sind an Lager verfügbar." msgstr "In den Warenkorb"
#: includes/StockManager.php #: includes/Admin/ProductData.php:108
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)" msgid ""
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)" "Allow draft and private products in the selection. Useful when products "
"should only be sold as part of a composition."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn "
"Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/StockManager.php #: includes/Admin/Settings.php:65
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)" msgid ""
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)" "Allow draft and private products to appear in composable product selections. "
"Useful when products should only be sold as part of a composition, not "
"individually."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte "
"anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung "
"verkauft werden sollen, nicht einzeln."
#: templates/product-selector.twig #: includes/Admin/ProductData.php:126
msgid "Out of stock" msgid "By Category"
msgstr "Nicht an Lager" msgstr "Nach Kategorie"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:128
msgid "Only" msgid "By SKU"
msgstr "Nur" msgstr "Nach Artikelnummer"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:127
msgid "left" msgid "By Tag"
msgstr "übrig" msgstr "Nach Schlagwort"
#: templates/product-selector.twig #: templates/product-selector.html.twig
msgid "Choose up to"
msgstr "Wähle bis zu"
#: includes/Admin/ProductData.php:34
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Plugin.php:132
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/Admin/Settings.php:21
msgid "Composable Products"
msgstr "Zusammenstellbare Produkte"
#: includes/Admin/Settings.php:34
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php:36
msgid "Configure default settings for composable products."
msgstr "Konfiguriere die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php:52
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php:40
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php:41
msgid "Default number of items customers can select."
msgstr ""
"Standardanzahl der Artikel, die Kunden auswählen können (z.B. 5 Sticker für "
"CHF 50.-)."
#: includes/Admin/Settings.php:79
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:72
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:86
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Admin/ProductData.php:196
msgid "Enter product SKUs separated by commas."
msgstr "Gib Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php:83
msgid "Enter the fixed price for this composable product."
msgstr "Gib den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php:82
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php:74 includes/Admin/Settings.php:59
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php:53
msgid "How to calculate the price of composable products."
msgstr ""
"Wie der Preis für zusammenstellbare Produkte berechnet wird (z.B. Festpreis "
"CHF 50.- oder Summe)."
#: includes/Admin/ProductData.php:69
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php:123
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: templates/product-selector.html.twig
msgid "In stock" msgid "In stock"
msgstr "An Lager" msgstr "An Lager"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:107 includes/Admin/Settings.php:64
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfiguriere die Produktkriterien im Admin-Bereich."
#: includes/Admin/Settings.php, includes/Admin/ProductData.php
msgid "Include Non-Public Products" msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen" msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php #: templates/product-selector.html.twig
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually." msgid "items from the selection below."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln." msgstr "Artikel aus der untenstehenden Auswahl."
#: includes/Admin/ProductData.php #: includes/Plugin.php:178
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition." msgid "Maximum items selected"
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen." msgstr ""
#: includes/Admin/ProductData.php #: includes/Admin/ProductData.php:55
msgid ""
"Maximum number of items customers can select. Leave empty to use global "
"default."
msgstr ""
"Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um "
"den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php:113
msgid "No"
msgstr "Nein"
#: templates/product-selector.html.twig
msgid ""
"No products available for selection. Please configure the product criteria "
"in the admin panel."
msgstr ""
"Keine Produkte zur Auswahl verfügbar. Bitte konfiguriere die "
"Produktkriterien im Admin-Bereich."
#: includes/CartHandler.php:115
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: templates/product-selector.html.twig
msgid "Only"
msgstr "Nur"
#. translators: 1: product name, 2: stock quantity
#: includes/StockManager.php:69
#, php-format
msgid "Only %2$d of \"%1$s\" are available in stock."
msgstr "Nur %2$d von \"%1$s\" sind an Lager verfügbar."
#: templates/product-selector.html.twig
msgid "Out of stock"
msgstr "Nicht an Lager"
#: includes/CartHandler.php:84 includes/CartHandler.php:100
msgid "Please select at least one product."
msgstr "Bitte wähle mindestens ein Produkt aus."
#: includes/Plugin.php:179
msgid "Please select at least one item"
msgstr ""
#: includes/Plugin.php:177
msgid "Please select items"
msgstr ""
#: includes/Admin/ProductData.php:68
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php:195
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php:198
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: templates/product-selector.html.twig
msgid "Select Your Products"
msgstr "Wähle deine Produkte"
#: includes/Admin/ProductData.php:138
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php:165
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php:159
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php:186
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/CartHandler.php:191
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php:122
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php:54
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/Settings.php:71
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php:78
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php:85
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#. translators: 1: product name, 2: quantity, 3: remaining stock
#: includes/StockManager.php:168
#, php-format
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
#. translators: 1: product name, 2: quantity, 3: new stock
#: includes/StockManager.php:235
#, php-format
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
#: includes/Admin/ProductData.php:73 includes/Admin/Settings.php:58
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: templates/product-selector.html.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: includes/Admin/ProductData.php:72 includes/Admin/ProductData.php:111
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php:112
msgid "Yes" msgid "Yes"
msgstr "Ja" msgstr "Ja"
#: includes/Admin/ProductData.php #. translators: %d: selection limit
msgid "No" #: includes/CartHandler.php:95
msgstr "Nein" #, php-format
msgid "You can select a maximum of %d products."
msgstr "Du kannst maximal %d Produkte auswählen."
#: templates/product-selector.html.twig
msgid "left"
msgstr "übrig"
+271 -223
View File
@@ -4,8 +4,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Composable Products 1.0.0\n" "Project-Id-Version: WooCommerce Composable Products 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n" "Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/"
"POT-Creation-Date: 2024-12-31 00:00+0000\n" "issues\n"
"POT-Creation-Date: 2026-03-01 13:47+0100\n"
"PO-Revision-Date: 2024-12-31 00:00+0000\n" "PO-Revision-Date: 2024-12-31 00:00+0000\n"
"Last-Translator: Claude AI\n" "Last-Translator: Claude AI\n"
"Language-Team: German\n" "Language-Team: German\n"
@@ -15,250 +16,297 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: wc-composable-product.php #: wc-composable-product.php:44
msgid "WooCommerce Composable Products requires WooCommerce to be installed and active." msgid ""
msgstr "WooCommerce Composable Products erfordert eine installierte und aktive WooCommerce-Installation." "WooCommerce Composable Products requires WooCommerce to be installed and "
"active."
msgstr ""
"WooCommerce Composable Products erfordert eine installierte und aktive "
"WooCommerce-Installation."
#: wc-composable-product.php #: wc-composable-product.php:90
msgid "This plugin requires WooCommerce to be installed and active." msgid "This plugin requires WooCommerce to be installed and active."
msgstr "Dieses Plugin erfordert eine installierte und aktive WooCommerce-Installation." msgstr ""
"Dieses Plugin erfordert eine installierte und aktive WooCommerce-"
"Installation."
#: wc-composable-product.php #: wc-composable-product.php:91
msgid "Plugin Activation Error" msgid "Plugin Activation Error"
msgstr "Plugin-Aktivierungsfehler" msgstr "Plugin-Aktivierungsfehler"
#: includes/Admin/Settings.php #. translators: %s: product name
msgid "Composable Products" #: includes/StockManager.php:60
msgstr "Zusammenstellbare Produkte" #, php-format
#: includes/Admin/Settings.php
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php
msgid "Configure default settings for composable products."
msgstr "Konfigurieren Sie die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php
msgid "Default number of items customers can select."
msgstr "Standardanzahl der Artikel, die Kunden auswählen können."
#: includes/Admin/Settings.php
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php
msgid "How to calculate the price of composable products."
msgstr "Wie der Preis für zusammenstellbare Produkte berechnet wird."
#: includes/Admin/Settings.php
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: includes/Admin/Settings.php
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#: includes/Admin/Settings.php
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Plugin.php
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/ProductSelector.php, templates/product-selector.twig
msgid "Select Your Products"
msgstr "Wählen Sie Ihre Produkte"
#: templates/product-selector.twig
msgid "Choose up to"
msgstr "Wählen Sie bis zu"
#: templates/product-selector.twig
msgid "items from the selection below."
msgstr "Artikel aus der untenstehenden Auswahl."
#: templates/product-selector.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: templates/product-selector.twig
msgid "Add to Cart"
msgstr "In den Warenkorb"
#: includes/CartHandler.php
msgid "Please select at least one product."
msgstr "Bitte wählen Sie mindestens ein Produkt aus."
#: includes/CartHandler.php
msgid "You can select a maximum of %d products."
msgstr "Sie können maximal %d Produkte auswählen."
#: includes/CartHandler.php
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: includes/CartHandler.php
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Admin/ProductData.php
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/ProductData.php
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr "Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php
msgid "Enter the fixed price for this composable product."
msgstr "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: includes/Admin/ProductData.php
msgid "By Category"
msgstr "Nach Kategorie"
#: includes/Admin/ProductData.php
msgid "By Tag"
msgstr "Nach Schlagwort"
#: includes/Admin/ProductData.php
msgid "By SKU"
msgstr "Nach Artikelnummer"
#: includes/Admin/ProductData.php
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php
msgid "Enter product SKUs separated by commas."
msgstr "Geben Sie Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: includes/StockManager.php
msgid "\"%s\" is out of stock and cannot be selected." msgid "\"%s\" is out of stock and cannot be selected."
msgstr "\"%s\" ist nicht auf Lager und kann nicht ausgewählt werden." msgstr "\"%s\" ist nicht auf Lager und kann nicht ausgewählt werden."
#: includes/StockManager.php #: templates/product-selector.html.twig
msgid "Only %2$d of \"%1$s\" are available in stock." msgid "Add to Cart"
msgstr "Nur %2$d von \"%1$s\" sind auf Lager verfügbar." msgstr "In den Warenkorb"
#: includes/StockManager.php #: includes/Admin/ProductData.php:108
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)" msgid ""
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)" "Allow draft and private products in the selection. Useful when products "
"should only be sold as part of a composition."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn "
"Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/StockManager.php #: includes/Admin/Settings.php:65
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)" msgid ""
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)" "Allow draft and private products to appear in composable product selections. "
"Useful when products should only be sold as part of a composition, not "
"individually."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte "
"anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung "
"verkauft werden sollen, nicht einzeln."
#: templates/product-selector.twig #: includes/Admin/ProductData.php:126
msgid "Out of stock" msgid "By Category"
msgstr "Nicht auf Lager" msgstr "Nach Kategorie"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:128
msgid "Only" msgid "By SKU"
msgstr "Nur" msgstr "Nach Artikelnummer"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:127
msgid "left" msgid "By Tag"
msgstr "übrig" msgstr "Nach Schlagwort"
#: templates/product-selector.twig #: templates/product-selector.html.twig
msgid "Choose up to"
msgstr "Wählen Sie bis zu"
#: includes/Admin/ProductData.php:34
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Plugin.php:132
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/Admin/Settings.php:21
msgid "Composable Products"
msgstr "Zusammenstellbare Produkte"
#: includes/Admin/Settings.php:34
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php:36
msgid "Configure default settings for composable products."
msgstr ""
"Konfigurieren Sie die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php:52
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php:40
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php:41
msgid "Default number of items customers can select."
msgstr "Standardanzahl der Artikel, die Kunden auswählen können."
#: includes/Admin/Settings.php:79
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:72
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:86
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Admin/ProductData.php:196
msgid "Enter product SKUs separated by commas."
msgstr "Geben Sie Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php:83
msgid "Enter the fixed price for this composable product."
msgstr "Geben Sie den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php:82
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php:74 includes/Admin/Settings.php:59
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php:53
msgid "How to calculate the price of composable products."
msgstr "Wie der Preis für zusammenstellbare Produkte berechnet wird."
#: includes/Admin/ProductData.php:69
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php:123
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: templates/product-selector.html.twig
msgid "In stock" msgid "In stock"
msgstr "Auf Lager" msgstr "Auf Lager"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:107 includes/Admin/Settings.php:64
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfigurieren Sie die Produktkriterien im Admin-Bereich."
#: includes/Admin/Settings.php, includes/Admin/ProductData.php
msgid "Include Non-Public Products" msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen" msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php #: templates/product-selector.html.twig
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually." msgid "items from the selection below."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln." msgstr "Artikel aus der untenstehenden Auswahl."
#: includes/Admin/ProductData.php #: includes/Plugin.php:178
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition." msgid "Maximum items selected"
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen." msgstr ""
#: includes/Admin/ProductData.php #: includes/Admin/ProductData.php:55
msgid ""
"Maximum number of items customers can select. Leave empty to use global "
"default."
msgstr ""
"Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um "
"den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php:113
msgid "No"
msgstr "Nein"
#: templates/product-selector.html.twig
msgid ""
"No products available for selection. Please configure the product criteria "
"in the admin panel."
msgstr ""
"Keine Produkte zur Auswahl verfügbar. Bitte konfigurieren Sie die "
"Produktkriterien im Admin-Bereich."
#: includes/CartHandler.php:115
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: templates/product-selector.html.twig
msgid "Only"
msgstr "Nur"
#. translators: 1: product name, 2: stock quantity
#: includes/StockManager.php:69
#, php-format
msgid "Only %2$d of \"%1$s\" are available in stock."
msgstr "Nur %2$d von \"%1$s\" sind auf Lager verfügbar."
#: templates/product-selector.html.twig
msgid "Out of stock"
msgstr "Nicht auf Lager"
#: includes/CartHandler.php:84 includes/CartHandler.php:100
msgid "Please select at least one product."
msgstr "Bitte wählen Sie mindestens ein Produkt aus."
#: includes/Plugin.php:179
msgid "Please select at least one item"
msgstr ""
#: includes/Plugin.php:177
msgid "Please select items"
msgstr ""
#: includes/Admin/ProductData.php:68
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php:195
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php:198
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: templates/product-selector.html.twig
msgid "Select Your Products"
msgstr "Wählen Sie Ihre Produkte"
#: includes/Admin/ProductData.php:138
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php:165
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php:159
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php:186
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/CartHandler.php:191
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php:122
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php:54
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/Settings.php:71
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php:78
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php:85
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#. translators: 1: product name, 2: quantity, 3: remaining stock
#: includes/StockManager.php:168
#, php-format
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
#. translators: 1: product name, 2: quantity, 3: new stock
#: includes/StockManager.php:235
#, php-format
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
#: includes/Admin/ProductData.php:73 includes/Admin/Settings.php:58
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: templates/product-selector.html.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: includes/Admin/ProductData.php:72 includes/Admin/ProductData.php:111
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php:112
msgid "Yes" msgid "Yes"
msgstr "Ja" msgstr "Ja"
#: includes/Admin/ProductData.php #. translators: %d: selection limit
msgid "No" #: includes/CartHandler.php:95
msgstr "Nein" #, php-format
msgid "You can select a maximum of %d products."
msgstr "Sie können maximal %d Produkte auswählen."
#: templates/product-selector.html.twig
msgid "left"
msgstr "übrig"
+270 -223
View File
@@ -4,8 +4,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Composable Products 1.0.0\n" "Project-Id-Version: WooCommerce Composable Products 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n" "Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/"
"POT-Creation-Date: 2024-12-31 00:00+0000\n" "issues\n"
"POT-Creation-Date: 2026-03-01 13:47+0100\n"
"PO-Revision-Date: 2024-12-31 00:00+0000\n" "PO-Revision-Date: 2024-12-31 00:00+0000\n"
"Last-Translator: Claude AI\n" "Last-Translator: Claude AI\n"
"Language-Team: German\n" "Language-Team: German\n"
@@ -15,250 +16,296 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: wc-composable-product.php #: wc-composable-product.php:44
msgid "WooCommerce Composable Products requires WooCommerce to be installed and active." msgid ""
msgstr "WooCommerce Composable Products erfordert eine installierte und aktive WooCommerce-Installation." "WooCommerce Composable Products requires WooCommerce to be installed and "
"active."
msgstr ""
"WooCommerce Composable Products erfordert eine installierte und aktive "
"WooCommerce-Installation."
#: wc-composable-product.php #: wc-composable-product.php:90
msgid "This plugin requires WooCommerce to be installed and active." msgid "This plugin requires WooCommerce to be installed and active."
msgstr "Dieses Plugin erfordert eine installierte und aktive WooCommerce-Installation." msgstr ""
"Dieses Plugin erfordert eine installierte und aktive WooCommerce-"
"Installation."
#: wc-composable-product.php #: wc-composable-product.php:91
msgid "Plugin Activation Error" msgid "Plugin Activation Error"
msgstr "Plugin-Aktivierungsfehler" msgstr "Plugin-Aktivierungsfehler"
#: includes/Admin/Settings.php #. translators: %s: product name
msgid "Composable Products" #: includes/StockManager.php:60
msgstr "Zusammenstellbare Produkte" #, php-format
#: includes/Admin/Settings.php
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php
msgid "Configure default settings for composable products."
msgstr "Konfiguriere die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php
msgid "Default number of items customers can select."
msgstr "Standardanzahl der Artikel, die Kunden auswählen können."
#: includes/Admin/Settings.php
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php
msgid "How to calculate the price of composable products."
msgstr "Wie der Preis für zusammenstellbare Produkte berechnet wird."
#: includes/Admin/Settings.php
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: includes/Admin/Settings.php
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#: includes/Admin/Settings.php
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Plugin.php
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/ProductSelector.php, templates/product-selector.twig
msgid "Select Your Products"
msgstr "Wähle deine Produkte"
#: templates/product-selector.twig
msgid "Choose up to"
msgstr "Wähle bis zu"
#: templates/product-selector.twig
msgid "items from the selection below."
msgstr "Artikel aus der untenstehenden Auswahl."
#: templates/product-selector.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: templates/product-selector.twig
msgid "Add to Cart"
msgstr "In den Warenkorb"
#: includes/CartHandler.php
msgid "Please select at least one product."
msgstr "Bitte wähle mindestens ein Produkt aus."
#: includes/CartHandler.php
msgid "You can select a maximum of %d products."
msgstr "Du kannst maximal %d Produkte auswählen."
#: includes/CartHandler.php
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: includes/CartHandler.php
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Admin/ProductData.php
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/ProductData.php
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr "Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php
msgid "Enter the fixed price for this composable product."
msgstr "Gib den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: includes/Admin/ProductData.php
msgid "By Category"
msgstr "Nach Kategorie"
#: includes/Admin/ProductData.php
msgid "By Tag"
msgstr "Nach Schlagwort"
#: includes/Admin/ProductData.php
msgid "By SKU"
msgstr "Nach Artikelnummer"
#: includes/Admin/ProductData.php
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php
msgid "Enter product SKUs separated by commas."
msgstr "Gib Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: includes/StockManager.php
msgid "\"%s\" is out of stock and cannot be selected." msgid "\"%s\" is out of stock and cannot be selected."
msgstr "\"%s\" ist nicht auf Lager und kann nicht ausgewählt werden." msgstr "\"%s\" ist nicht auf Lager und kann nicht ausgewählt werden."
#: includes/StockManager.php #: templates/product-selector.html.twig
msgid "Only %2$d of \"%1$s\" are available in stock." msgid "Add to Cart"
msgstr "Nur %2$d von \"%1$s\" sind auf Lager verfügbar." msgstr "In den Warenkorb"
#: includes/StockManager.php #: includes/Admin/ProductData.php:108
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)" msgid ""
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)" "Allow draft and private products in the selection. Useful when products "
"should only be sold as part of a composition."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn "
"Produkte nur als Teil einer Zusammenstellung verkauft werden sollen."
#: includes/StockManager.php #: includes/Admin/Settings.php:65
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)" msgid ""
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)" "Allow draft and private products to appear in composable product selections. "
"Useful when products should only be sold as part of a composition, not "
"individually."
msgstr ""
"Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte "
"anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung "
"verkauft werden sollen, nicht einzeln."
#: templates/product-selector.twig #: includes/Admin/ProductData.php:126
msgid "Out of stock" msgid "By Category"
msgstr "Nicht auf Lager" msgstr "Nach Kategorie"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:128
msgid "Only" msgid "By SKU"
msgstr "Nur" msgstr "Nach Artikelnummer"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:127
msgid "left" msgid "By Tag"
msgstr "übrig" msgstr "Nach Schlagwort"
#: templates/product-selector.twig #: templates/product-selector.html.twig
msgid "Choose up to"
msgstr "Wähle bis zu"
#: includes/Admin/ProductData.php:34
msgid "Composable Options"
msgstr "Zusammenstellungsoptionen"
#: includes/Plugin.php:132
msgid "Composable product"
msgstr "Zusammenstellbares Produkt"
#: includes/Admin/Settings.php:21
msgid "Composable Products"
msgstr "Zusammenstellbare Produkte"
#: includes/Admin/Settings.php:34
msgid "Composable Products Settings"
msgstr "Einstellungen für zusammenstellbare Produkte"
#: includes/Admin/Settings.php:36
msgid "Configure default settings for composable products."
msgstr "Konfiguriere die Standardeinstellungen für zusammenstellbare Produkte."
#: includes/Admin/Settings.php:52
msgid "Default Pricing Mode"
msgstr "Standard-Preismodus"
#: includes/Admin/Settings.php:40
msgid "Default Selection Limit"
msgstr "Standard-Auswahllimit"
#: includes/Admin/Settings.php:41
msgid "Default number of items customers can select."
msgstr "Standardanzahl der Artikel, die Kunden auswählen können."
#: includes/Admin/Settings.php:79
msgid "Display individual product prices in the selection interface."
msgstr "Einzelne Produktpreise in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:72
msgid "Display product images in the selection interface."
msgstr "Produktbilder in der Auswahlschnittstelle anzeigen."
#: includes/Admin/Settings.php:86
msgid "Display the total price as customers make selections."
msgstr "Den Gesamtpreis anzeigen, während Kunden Auswahlen treffen."
#: includes/Admin/ProductData.php:196
msgid "Enter product SKUs separated by commas."
msgstr "Gib Produkt-Artikelnummern durch Kommas getrennt ein."
#: includes/Admin/ProductData.php:83
msgid "Enter the fixed price for this composable product."
msgstr "Gib den Festpreis für dieses zusammenstellbare Produkt ein."
#: includes/Admin/ProductData.php:82
msgid "Fixed Price"
msgstr "Festpreis"
#: includes/Admin/ProductData.php:74 includes/Admin/Settings.php:59
msgid "Fixed price"
msgstr "Festpreis"
#: includes/Admin/Settings.php:53
msgid "How to calculate the price of composable products."
msgstr "Wie der Preis für zusammenstellbare Produkte berechnet wird."
#: includes/Admin/ProductData.php:69
msgid "How to calculate the price."
msgstr "Wie der Preis berechnet wird."
#: includes/Admin/ProductData.php:123
msgid "How to select available products."
msgstr "Wie verfügbare Produkte ausgewählt werden."
#: templates/product-selector.html.twig
msgid "In stock" msgid "In stock"
msgstr "Auf Lager" msgstr "Auf Lager"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:107 includes/Admin/Settings.php:64
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr "Keine Produkte zur Auswahl verfügbar. Bitte konfiguriere die Produktkriterien im Admin-Bereich."
#: includes/Admin/Settings.php, includes/Admin/ProductData.php
msgid "Include Non-Public Products" msgid "Include Non-Public Products"
msgstr "Nicht-öffentliche Produkte einbeziehen" msgstr "Nicht-öffentliche Produkte einbeziehen"
#: includes/Admin/Settings.php #: templates/product-selector.html.twig
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually." msgid "items from the selection below."
msgstr "Entwürfe und private Produkte in der Auswahl zusammenstellbarer Produkte anzeigen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen, nicht einzeln." msgstr "Artikel aus der untenstehenden Auswahl."
#: includes/Admin/ProductData.php #: includes/Plugin.php:178
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition." msgid "Maximum items selected"
msgstr "Entwürfe und private Produkte in der Auswahl zulassen. Nützlich, wenn Produkte nur als Teil einer Zusammenstellung verkauft werden sollen." msgstr ""
#: includes/Admin/ProductData.php #: includes/Admin/ProductData.php:55
msgid ""
"Maximum number of items customers can select. Leave empty to use global "
"default."
msgstr ""
"Maximale Anzahl der Artikel, die Kunden auswählen können. Leer lassen, um "
"den globalen Standard zu verwenden."
#: includes/Admin/ProductData.php:113
msgid "No"
msgstr "Nein"
#: templates/product-selector.html.twig
msgid ""
"No products available for selection. Please configure the product criteria "
"in the admin panel."
msgstr ""
"Keine Produkte zur Auswahl verfügbar. Bitte konfiguriere die "
"Produktkriterien im Admin-Bereich."
#: includes/CartHandler.php:115
msgid "One or more selected products are not available."
msgstr "Ein oder mehrere ausgewählte Produkte sind nicht verfügbar."
#: templates/product-selector.html.twig
msgid "Only"
msgstr "Nur"
#. translators: 1: product name, 2: stock quantity
#: includes/StockManager.php:69
#, php-format
msgid "Only %2$d of \"%1$s\" are available in stock."
msgstr "Nur %2$d von \"%1$s\" sind auf Lager verfügbar."
#: templates/product-selector.html.twig
msgid "Out of stock"
msgstr "Nicht auf Lager"
#: includes/CartHandler.php:84 includes/CartHandler.php:100
msgid "Please select at least one product."
msgstr "Bitte wähle mindestens ein Produkt aus."
#: includes/Plugin.php:179
msgid "Please select at least one item"
msgstr ""
#: includes/Plugin.php:177
msgid "Please select items"
msgstr ""
#: includes/Admin/ProductData.php:68
msgid "Pricing Mode"
msgstr "Preismodus"
#: includes/Admin/ProductData.php:195
msgid "Product SKUs"
msgstr "Produkt-Artikelnummern"
#: includes/Admin/ProductData.php:198
msgid "SKU-1, SKU-2, SKU-3"
msgstr "ART-1, ART-2, ART-3"
#: templates/product-selector.html.twig
msgid "Select Your Products"
msgstr "Wähle deine Produkte"
#: includes/Admin/ProductData.php:138
msgid "Select Categories"
msgstr "Kategorien auswählen"
#: includes/Admin/ProductData.php:165
msgid "Select Tags"
msgstr "Schlagwörter auswählen"
#: includes/Admin/ProductData.php:159
msgid "Select product categories to include."
msgstr "Produktkategorien auswählen, die einbezogen werden sollen."
#: includes/Admin/ProductData.php:186
msgid "Select product tags to include."
msgstr "Produkt-Schlagwörter auswählen, die einbezogen werden sollen."
#: includes/CartHandler.php:191
msgid "Selected Products"
msgstr "Ausgewählte Produkte"
#: includes/Admin/ProductData.php:122
msgid "Selection Criteria"
msgstr "Auswahlkriterien"
#: includes/Admin/ProductData.php:54
msgid "Selection Limit"
msgstr "Auswahllimit"
#: includes/Admin/Settings.php:71
msgid "Show Product Images"
msgstr "Produktbilder anzeigen"
#: includes/Admin/Settings.php:78
msgid "Show Product Prices"
msgstr "Produktpreise anzeigen"
#: includes/Admin/Settings.php:85
msgid "Show Total Price"
msgstr "Gesamtpreis anzeigen"
#. translators: 1: product name, 2: quantity, 3: remaining stock
#: includes/StockManager.php:168
#, php-format
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr "Lagerbestand reduziert für \"%1$s\": -%2$d (verbleibend: %3$d)"
#. translators: 1: product name, 2: quantity, 3: new stock
#: includes/StockManager.php:235
#, php-format
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr "Lagerbestand wiederhergestellt für \"%1$s\": +%2$d (gesamt: %3$d)"
#: includes/Admin/ProductData.php:73 includes/Admin/Settings.php:58
msgid "Sum of selected products"
msgstr "Summe der ausgewählten Produkte"
#: templates/product-selector.html.twig
msgid "Total Price:"
msgstr "Gesamtpreis:"
#: includes/Admin/ProductData.php:72 includes/Admin/ProductData.php:111
msgid "Use global default"
msgstr "Globalen Standard verwenden"
#: includes/Admin/ProductData.php:112
msgid "Yes" msgid "Yes"
msgstr "Ja" msgstr "Ja"
#: includes/Admin/ProductData.php #. translators: %d: selection limit
msgid "No" #: includes/CartHandler.php:95
msgstr "Nein" #, php-format
msgid "You can select a maximum of %d products."
msgstr "Du kannst maximal %d Produkte auswählen."
#: templates/product-selector.html.twig
msgid "left"
msgstr "übrig"
+273 -222
View File
@@ -4,8 +4,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Composable Products 1.0.0\n" "Project-Id-Version: WooCommerce Composable Products 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n" "Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/"
"POT-Creation-Date: 2024-12-31 00:00+0000\n" "issues\n"
"POT-Creation-Date: 2026-03-01 13:47+0100\n"
"PO-Revision-Date: 2024-12-31 00:00+0000\n" "PO-Revision-Date: 2024-12-31 00:00+0000\n"
"Last-Translator: Claude AI\n" "Last-Translator: Claude AI\n"
"Language-Team: French (Switzerland)\n" "Language-Team: French (Switzerland)\n"
@@ -15,250 +16,300 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: wc-composable-product.php #: wc-composable-product.php:44
msgid "WooCommerce Composable Products requires WooCommerce to be installed and active." msgid ""
msgstr "WooCommerce Composable Products nécessite une installation WooCommerce active." "WooCommerce Composable Products requires WooCommerce to be installed and "
"active."
msgstr ""
"WooCommerce Composable Products nécessite une installation WooCommerce "
"active."
#: wc-composable-product.php #: wc-composable-product.php:90
msgid "This plugin requires WooCommerce to be installed and active." msgid "This plugin requires WooCommerce to be installed and active."
msgstr "Cette extension nécessite une installation WooCommerce active." msgstr "Cette extension nécessite une installation WooCommerce active."
#: wc-composable-product.php #: wc-composable-product.php:91
msgid "Plugin Activation Error" msgid "Plugin Activation Error"
msgstr "Erreur d'activation de l'extension" msgstr "Erreur d'activation de l'extension"
#: includes/Admin/Settings.php #. translators: %s: product name
msgid "Composable Products" #: includes/StockManager.php:60
msgstr "Produits Composables" #, php-format
#: includes/Admin/Settings.php
msgid "Composable Products Settings"
msgstr "Paramètres des produits composables"
#: includes/Admin/Settings.php
msgid "Configure default settings for composable products."
msgstr "Configurez les paramètres par défaut pour les produits composables."
#: includes/Admin/Settings.php
msgid "Default Selection Limit"
msgstr "Limite de sélection par défaut"
#: includes/Admin/Settings.php
msgid "Default number of items customers can select."
msgstr "Nombre par défaut d'articles que les clients peuvent sélectionner (p. ex. 5 stickers pour CHF 50.-)."
#: includes/Admin/Settings.php
msgid "Default Pricing Mode"
msgstr "Mode de tarification par défaut"
#: includes/Admin/Settings.php
msgid "How to calculate the price of composable products."
msgstr "Comment calculer le prix des produits composables (p. ex. prix fixe CHF 50.- ou somme)."
#: includes/Admin/Settings.php
msgid "Sum of selected products"
msgstr "Somme des produits sélectionnés"
#: includes/Admin/Settings.php
msgid "Fixed price"
msgstr "Prix fixe"
#: includes/Admin/Settings.php
msgid "Show Product Images"
msgstr "Afficher les images des produits"
#: includes/Admin/Settings.php
msgid "Display product images in the selection interface."
msgstr "Afficher les images des produits dans l'interface de sélection."
#: includes/Admin/Settings.php
msgid "Show Product Prices"
msgstr "Afficher les prix des produits"
#: includes/Admin/Settings.php
msgid "Display individual product prices in the selection interface."
msgstr "Afficher les prix individuels des produits dans l'interface de sélection."
#: includes/Admin/Settings.php
msgid "Show Total Price"
msgstr "Afficher le prix total"
#: includes/Admin/Settings.php
msgid "Display the total price as customers make selections."
msgstr "Afficher le prix total pendant que les clients font leurs sélections."
#: includes/Plugin.php
msgid "Composable product"
msgstr "Produit composable"
#: includes/ProductSelector.php, templates/product-selector.twig
msgid "Select Your Products"
msgstr "Sélectionnez vos produits"
#: templates/product-selector.twig
msgid "Choose up to"
msgstr "Choisissez jusqu'à"
#: templates/product-selector.twig
msgid "items from the selection below."
msgstr "articles de la sélection ci-dessous."
#: templates/product-selector.twig
msgid "Total Price:"
msgstr "Prix total :"
#: templates/product-selector.twig
msgid "Add to Cart"
msgstr "Ajouter au panier"
#: includes/CartHandler.php
msgid "Please select at least one product."
msgstr "Veuillez sélectionner au moins un produit."
#: includes/CartHandler.php
msgid "You can select a maximum of %d products."
msgstr "Vous pouvez sélectionner un maximum de %d produits."
#: includes/CartHandler.php
msgid "One or more selected products are not available."
msgstr "Un ou plusieurs produits sélectionnés ne sont pas disponibles."
#: includes/CartHandler.php
msgid "Selected Products"
msgstr "Produits sélectionnés"
#: includes/Admin/ProductData.php
msgid "Composable Options"
msgstr "Options de composition"
#: includes/Admin/ProductData.php
msgid "Selection Limit"
msgstr "Limite de sélection"
#: includes/Admin/ProductData.php
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr "Nombre maximum d'articles que les clients peuvent sélectionner. Laisser vide pour utiliser la valeur par défaut globale."
#: includes/Admin/ProductData.php
msgid "Pricing Mode"
msgstr "Mode de tarification"
#: includes/Admin/ProductData.php
msgid "How to calculate the price."
msgstr "Comment calculer le prix."
#: includes/Admin/ProductData.php
msgid "Fixed Price"
msgstr "Prix fixe"
#: includes/Admin/ProductData.php
msgid "Enter the fixed price for this composable product."
msgstr "Entrez le prix fixe pour ce produit composable."
#: includes/Admin/ProductData.php
msgid "Use global default"
msgstr "Utiliser la valeur par défaut globale"
#: includes/Admin/ProductData.php
msgid "Selection Criteria"
msgstr "Critères de sélection"
#: includes/Admin/ProductData.php
msgid "How to select available products."
msgstr "Comment sélectionner les produits disponibles."
#: includes/Admin/ProductData.php
msgid "By Category"
msgstr "Par catégorie"
#: includes/Admin/ProductData.php
msgid "By Tag"
msgstr "Par étiquette"
#: includes/Admin/ProductData.php
msgid "By SKU"
msgstr "Par référence"
#: includes/Admin/ProductData.php
msgid "Select Categories"
msgstr "Sélectionner les catégories"
#: includes/Admin/ProductData.php
msgid "Select product categories to include."
msgstr "Sélectionner les catégories de produits à inclure."
#: includes/Admin/ProductData.php
msgid "Select Tags"
msgstr "Sélectionner les étiquettes"
#: includes/Admin/ProductData.php
msgid "Select product tags to include."
msgstr "Sélectionner les étiquettes de produits à inclure."
#: includes/Admin/ProductData.php
msgid "Product SKUs"
msgstr "Références des produits"
#: includes/Admin/ProductData.php
msgid "Enter product SKUs separated by commas."
msgstr "Entrez les références des produits séparées par des virgules."
#: includes/Admin/ProductData.php
msgid "SKU-1, SKU-2, SKU-3"
msgstr "REF-1, REF-2, REF-3"
#: includes/StockManager.php
msgid "\"%s\" is out of stock and cannot be selected." msgid "\"%s\" is out of stock and cannot be selected."
msgstr "\"%s\" est en rupture de stock et ne peut pas être sélectionné." msgstr "\"%s\" est en rupture de stock et ne peut pas être sélectionné."
#: includes/StockManager.php #: templates/product-selector.html.twig
msgid "Only %2$d of \"%1$s\" are available in stock." msgid "Add to Cart"
msgstr "Seulement %2$d de \"%1$s\" sont disponibles en stock." msgstr "Ajouter au panier"
#: includes/StockManager.php #: includes/Admin/ProductData.php:108
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)" msgid ""
msgstr "Stock réduit pour \"%1$s\": -%2$d (restant: %3$d)" "Allow draft and private products in the selection. Useful when products "
"should only be sold as part of a composition."
msgstr ""
"Autoriser les brouillons et les produits privés dans la sélection. Utile "
"lorsque les produits ne doivent être vendus que dans le cadre d'une "
"composition."
#: includes/StockManager.php #: includes/Admin/Settings.php:65
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)" msgid ""
msgstr "Stock restauré pour \"%1$s\": +%2$d (total: %3$d)" "Allow draft and private products to appear in composable product selections. "
"Useful when products should only be sold as part of a composition, not "
"individually."
msgstr ""
"Autoriser les brouillons et les produits privés dans les sélections de "
"produits composables. Utile lorsque les produits ne doivent être vendus que "
"dans le cadre d'une composition, pas individuellement."
#: templates/product-selector.twig #: includes/Admin/ProductData.php:126
msgid "Out of stock" msgid "By Category"
msgstr "Rupture de stock" msgstr "Par catégorie"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:128
msgid "Only" msgid "By SKU"
msgstr "Seulement" msgstr "Par référence"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:127
msgid "left" msgid "By Tag"
msgstr "restant" msgstr "Par étiquette"
#: templates/product-selector.twig #: templates/product-selector.html.twig
msgid "Choose up to"
msgstr "Choisissez jusqu'à"
#: includes/Admin/ProductData.php:34
msgid "Composable Options"
msgstr "Options de composition"
#: includes/Plugin.php:132
msgid "Composable product"
msgstr "Produit composable"
#: includes/Admin/Settings.php:21
msgid "Composable Products"
msgstr "Produits Composables"
#: includes/Admin/Settings.php:34
msgid "Composable Products Settings"
msgstr "Paramètres des produits composables"
#: includes/Admin/Settings.php:36
msgid "Configure default settings for composable products."
msgstr "Configurez les paramètres par défaut pour les produits composables."
#: includes/Admin/Settings.php:52
msgid "Default Pricing Mode"
msgstr "Mode de tarification par défaut"
#: includes/Admin/Settings.php:40
msgid "Default Selection Limit"
msgstr "Limite de sélection par défaut"
#: includes/Admin/Settings.php:41
msgid "Default number of items customers can select."
msgstr ""
"Nombre par défaut d'articles que les clients peuvent sélectionner (p. ex. 5 "
"stickers pour CHF 50.-)."
#: includes/Admin/Settings.php:79
msgid "Display individual product prices in the selection interface."
msgstr ""
"Afficher les prix individuels des produits dans l'interface de sélection."
#: includes/Admin/Settings.php:72
msgid "Display product images in the selection interface."
msgstr "Afficher les images des produits dans l'interface de sélection."
#: includes/Admin/Settings.php:86
msgid "Display the total price as customers make selections."
msgstr "Afficher le prix total pendant que les clients font leurs sélections."
#: includes/Admin/ProductData.php:196
msgid "Enter product SKUs separated by commas."
msgstr "Entrez les références des produits séparées par des virgules."
#: includes/Admin/ProductData.php:83
msgid "Enter the fixed price for this composable product."
msgstr "Entrez le prix fixe pour ce produit composable."
#: includes/Admin/ProductData.php:82
msgid "Fixed Price"
msgstr "Prix fixe"
#: includes/Admin/ProductData.php:74 includes/Admin/Settings.php:59
msgid "Fixed price"
msgstr "Prix fixe"
#: includes/Admin/Settings.php:53
msgid "How to calculate the price of composable products."
msgstr ""
"Comment calculer le prix des produits composables (p. ex. prix fixe CHF 50.- "
"ou somme)."
#: includes/Admin/ProductData.php:69
msgid "How to calculate the price."
msgstr "Comment calculer le prix."
#: includes/Admin/ProductData.php:123
msgid "How to select available products."
msgstr "Comment sélectionner les produits disponibles."
#: templates/product-selector.html.twig
msgid "In stock" msgid "In stock"
msgstr "En stock" msgstr "En stock"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:107 includes/Admin/Settings.php:64
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr "Aucun produit disponible pour la sélection. Veuillez configurer les critères de produit dans le panneau d'administration."
#: includes/Admin/Settings.php, includes/Admin/ProductData.php
msgid "Include Non-Public Products" msgid "Include Non-Public Products"
msgstr "Inclure les produits non publics" msgstr "Inclure les produits non publics"
#: includes/Admin/Settings.php #: templates/product-selector.html.twig
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually." msgid "items from the selection below."
msgstr "Autoriser les brouillons et les produits privés dans les sélections de produits composables. Utile lorsque les produits ne doivent être vendus que dans le cadre d'une composition, pas individuellement." msgstr "articles de la sélection ci-dessous."
#: includes/Admin/ProductData.php #: includes/Plugin.php:178
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition." msgid "Maximum items selected"
msgstr "Autoriser les brouillons et les produits privés dans la sélection. Utile lorsque les produits ne doivent être vendus que dans le cadre d'une composition." msgstr ""
#: includes/Admin/ProductData.php #: includes/Admin/ProductData.php:55
msgid ""
"Maximum number of items customers can select. Leave empty to use global "
"default."
msgstr ""
"Nombre maximum d'articles que les clients peuvent sélectionner. Laisser vide "
"pour utiliser la valeur par défaut globale."
#: includes/Admin/ProductData.php:113
msgid "No"
msgstr "Non"
#: templates/product-selector.html.twig
msgid ""
"No products available for selection. Please configure the product criteria "
"in the admin panel."
msgstr ""
"Aucun produit disponible pour la sélection. Veuillez configurer les critères "
"de produit dans le panneau d'administration."
#: includes/CartHandler.php:115
msgid "One or more selected products are not available."
msgstr "Un ou plusieurs produits sélectionnés ne sont pas disponibles."
#: templates/product-selector.html.twig
msgid "Only"
msgstr "Seulement"
#. translators: 1: product name, 2: stock quantity
#: includes/StockManager.php:69
#, php-format
msgid "Only %2$d of \"%1$s\" are available in stock."
msgstr "Seulement %2$d de \"%1$s\" sont disponibles en stock."
#: templates/product-selector.html.twig
msgid "Out of stock"
msgstr "Rupture de stock"
#: includes/CartHandler.php:84 includes/CartHandler.php:100
msgid "Please select at least one product."
msgstr "Veuillez sélectionner au moins un produit."
#: includes/Plugin.php:179
msgid "Please select at least one item"
msgstr ""
#: includes/Plugin.php:177
msgid "Please select items"
msgstr ""
#: includes/Admin/ProductData.php:68
msgid "Pricing Mode"
msgstr "Mode de tarification"
#: includes/Admin/ProductData.php:195
msgid "Product SKUs"
msgstr "Références des produits"
#: includes/Admin/ProductData.php:198
msgid "SKU-1, SKU-2, SKU-3"
msgstr "REF-1, REF-2, REF-3"
#: templates/product-selector.html.twig
msgid "Select Your Products"
msgstr "Sélectionnez vos produits"
#: includes/Admin/ProductData.php:138
msgid "Select Categories"
msgstr "Sélectionner les catégories"
#: includes/Admin/ProductData.php:165
msgid "Select Tags"
msgstr "Sélectionner les étiquettes"
#: includes/Admin/ProductData.php:159
msgid "Select product categories to include."
msgstr "Sélectionner les catégories de produits à inclure."
#: includes/Admin/ProductData.php:186
msgid "Select product tags to include."
msgstr "Sélectionner les étiquettes de produits à inclure."
#: includes/CartHandler.php:191
msgid "Selected Products"
msgstr "Produits sélectionnés"
#: includes/Admin/ProductData.php:122
msgid "Selection Criteria"
msgstr "Critères de sélection"
#: includes/Admin/ProductData.php:54
msgid "Selection Limit"
msgstr "Limite de sélection"
#: includes/Admin/Settings.php:71
msgid "Show Product Images"
msgstr "Afficher les images des produits"
#: includes/Admin/Settings.php:78
msgid "Show Product Prices"
msgstr "Afficher les prix des produits"
#: includes/Admin/Settings.php:85
msgid "Show Total Price"
msgstr "Afficher le prix total"
#. translators: 1: product name, 2: quantity, 3: remaining stock
#: includes/StockManager.php:168
#, php-format
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr "Stock réduit pour \"%1$s\": -%2$d (restant: %3$d)"
#. translators: 1: product name, 2: quantity, 3: new stock
#: includes/StockManager.php:235
#, php-format
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr "Stock restauré pour \"%1$s\": +%2$d (total: %3$d)"
#: includes/Admin/ProductData.php:73 includes/Admin/Settings.php:58
msgid "Sum of selected products"
msgstr "Somme des produits sélectionnés"
#: templates/product-selector.html.twig
msgid "Total Price:"
msgstr "Prix total :"
#: includes/Admin/ProductData.php:72 includes/Admin/ProductData.php:111
msgid "Use global default"
msgstr "Utiliser la valeur par défaut globale"
#: includes/Admin/ProductData.php:112
msgid "Yes" msgid "Yes"
msgstr "Oui" msgstr "Oui"
#: includes/Admin/ProductData.php #. translators: %d: selection limit
msgid "No" #: includes/CartHandler.php:95
msgstr "Non" #, php-format
msgid "You can select a maximum of %d products."
msgstr "Vous pouvez sélectionner un maximum de %d produits."
#: templates/product-selector.html.twig
msgid "left"
msgstr "restant"
+272 -222
View File
@@ -4,8 +4,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Composable Products 1.0.0\n" "Project-Id-Version: WooCommerce Composable Products 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n" "Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/"
"POT-Creation-Date: 2024-12-31 00:00+0000\n" "issues\n"
"POT-Creation-Date: 2026-03-01 13:47+0100\n"
"PO-Revision-Date: 2024-12-31 00:00+0000\n" "PO-Revision-Date: 2024-12-31 00:00+0000\n"
"Last-Translator: Claude AI\n" "Last-Translator: Claude AI\n"
"Language-Team: Italian (Switzerland)\n" "Language-Team: Italian (Switzerland)\n"
@@ -15,250 +16,299 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: wc-composable-product.php #: wc-composable-product.php:44
msgid "WooCommerce Composable Products requires WooCommerce to be installed and active." msgid ""
msgstr "WooCommerce Composable Products richiede un'installazione WooCommerce attiva." "WooCommerce Composable Products requires WooCommerce to be installed and "
"active."
msgstr ""
"WooCommerce Composable Products richiede un'installazione WooCommerce attiva."
#: wc-composable-product.php #: wc-composable-product.php:90
msgid "This plugin requires WooCommerce to be installed and active." msgid "This plugin requires WooCommerce to be installed and active."
msgstr "Questo plugin richiede un'installazione WooCommerce attiva." msgstr "Questo plugin richiede un'installazione WooCommerce attiva."
#: wc-composable-product.php #: wc-composable-product.php:91
msgid "Plugin Activation Error" msgid "Plugin Activation Error"
msgstr "Errore di attivazione del plugin" msgstr "Errore di attivazione del plugin"
#: includes/Admin/Settings.php #. translators: %s: product name
msgid "Composable Products" #: includes/StockManager.php:60
msgstr "Prodotti Componibili" #, php-format
#: includes/Admin/Settings.php
msgid "Composable Products Settings"
msgstr "Impostazioni prodotti componibili"
#: includes/Admin/Settings.php
msgid "Configure default settings for composable products."
msgstr "Configurare le impostazioni predefinite per i prodotti componibili."
#: includes/Admin/Settings.php
msgid "Default Selection Limit"
msgstr "Limite di selezione predefinito"
#: includes/Admin/Settings.php
msgid "Default number of items customers can select."
msgstr "Numero predefinito di articoli che i clienti possono selezionare (p. es. 5 adesivi per CHF 50.-)."
#: includes/Admin/Settings.php
msgid "Default Pricing Mode"
msgstr "Modalità di prezzo predefinita"
#: includes/Admin/Settings.php
msgid "How to calculate the price of composable products."
msgstr "Come calcolare il prezzo dei prodotti componibili (p. es. prezzo fisso CHF 50.- o somma)."
#: includes/Admin/Settings.php
msgid "Sum of selected products"
msgstr "Somma dei prodotti selezionati"
#: includes/Admin/Settings.php
msgid "Fixed price"
msgstr "Prezzo fisso"
#: includes/Admin/Settings.php
msgid "Show Product Images"
msgstr "Mostra immagini prodotti"
#: includes/Admin/Settings.php
msgid "Display product images in the selection interface."
msgstr "Visualizzare le immagini dei prodotti nell'interfaccia di selezione."
#: includes/Admin/Settings.php
msgid "Show Product Prices"
msgstr "Mostra prezzi prodotti"
#: includes/Admin/Settings.php
msgid "Display individual product prices in the selection interface."
msgstr "Visualizzare i prezzi individuali dei prodotti nell'interfaccia di selezione."
#: includes/Admin/Settings.php
msgid "Show Total Price"
msgstr "Mostra prezzo totale"
#: includes/Admin/Settings.php
msgid "Display the total price as customers make selections."
msgstr "Visualizzare il prezzo totale mentre i clienti effettuano le selezioni."
#: includes/Plugin.php
msgid "Composable product"
msgstr "Prodotto componibile"
#: includes/ProductSelector.php, templates/product-selector.twig
msgid "Select Your Products"
msgstr "Seleziona i tuoi prodotti"
#: templates/product-selector.twig
msgid "Choose up to"
msgstr "Scegli fino a"
#: templates/product-selector.twig
msgid "items from the selection below."
msgstr "articoli dalla selezione qui sotto."
#: templates/product-selector.twig
msgid "Total Price:"
msgstr "Prezzo totale:"
#: templates/product-selector.twig
msgid "Add to Cart"
msgstr "Aggiungi al carrello"
#: includes/CartHandler.php
msgid "Please select at least one product."
msgstr "Seleziona almeno un prodotto."
#: includes/CartHandler.php
msgid "You can select a maximum of %d products."
msgstr "Puoi selezionare un massimo di %d prodotti."
#: includes/CartHandler.php
msgid "One or more selected products are not available."
msgstr "Uno o più prodotti selezionati non sono disponibili."
#: includes/CartHandler.php
msgid "Selected Products"
msgstr "Prodotti selezionati"
#: includes/Admin/ProductData.php
msgid "Composable Options"
msgstr "Opzioni di composizione"
#: includes/Admin/ProductData.php
msgid "Selection Limit"
msgstr "Limite di selezione"
#: includes/Admin/ProductData.php
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr "Numero massimo di articoli che i clienti possono selezionare. Lasciare vuoto per utilizzare il valore predefinito globale."
#: includes/Admin/ProductData.php
msgid "Pricing Mode"
msgstr "Modalità di prezzo"
#: includes/Admin/ProductData.php
msgid "How to calculate the price."
msgstr "Come calcolare il prezzo."
#: includes/Admin/ProductData.php
msgid "Fixed Price"
msgstr "Prezzo fisso"
#: includes/Admin/ProductData.php
msgid "Enter the fixed price for this composable product."
msgstr "Inserisci il prezzo fisso per questo prodotto componibile."
#: includes/Admin/ProductData.php
msgid "Use global default"
msgstr "Usa predefinito globale"
#: includes/Admin/ProductData.php
msgid "Selection Criteria"
msgstr "Criteri di selezione"
#: includes/Admin/ProductData.php
msgid "How to select available products."
msgstr "Come selezionare i prodotti disponibili."
#: includes/Admin/ProductData.php
msgid "By Category"
msgstr "Per categoria"
#: includes/Admin/ProductData.php
msgid "By Tag"
msgstr "Per etichetta"
#: includes/Admin/ProductData.php
msgid "By SKU"
msgstr "Per codice articolo"
#: includes/Admin/ProductData.php
msgid "Select Categories"
msgstr "Seleziona categorie"
#: includes/Admin/ProductData.php
msgid "Select product categories to include."
msgstr "Selezionare le categorie di prodotti da includere."
#: includes/Admin/ProductData.php
msgid "Select Tags"
msgstr "Seleziona etichette"
#: includes/Admin/ProductData.php
msgid "Select product tags to include."
msgstr "Selezionare le etichette dei prodotti da includere."
#: includes/Admin/ProductData.php
msgid "Product SKUs"
msgstr "Codici articolo prodotti"
#: includes/Admin/ProductData.php
msgid "Enter product SKUs separated by commas."
msgstr "Inserire i codici articolo dei prodotti separati da virgole."
#: includes/Admin/ProductData.php
msgid "SKU-1, SKU-2, SKU-3"
msgstr "COD-1, COD-2, COD-3"
#: includes/StockManager.php
msgid "\"%s\" is out of stock and cannot be selected." msgid "\"%s\" is out of stock and cannot be selected."
msgstr "\"%s\" è esaurito e non può essere selezionato." msgstr "\"%s\" è esaurito e non può essere selezionato."
#: includes/StockManager.php #: templates/product-selector.html.twig
msgid "Only %2$d of \"%1$s\" are available in stock." msgid "Add to Cart"
msgstr "Solo %2$d di \"%1$s\" sono disponibili in magazzino." msgstr "Aggiungi al carrello"
#: includes/StockManager.php #: includes/Admin/ProductData.php:108
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)" msgid ""
msgstr "Giacenza ridotta per \"%1$s\": -%2$d (rimanenti: %3$d)" "Allow draft and private products in the selection. Useful when products "
"should only be sold as part of a composition."
msgstr ""
"Consenti bozze e prodotti privati nella selezione. Utile quando i prodotti "
"devono essere venduti solo come parte di una composizione."
#: includes/StockManager.php #: includes/Admin/Settings.php:65
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)" msgid ""
msgstr "Giacenza ripristinata per \"%1$s\": +%2$d (totale: %3$d)" "Allow draft and private products to appear in composable product selections. "
"Useful when products should only be sold as part of a composition, not "
"individually."
msgstr ""
"Consenti la visualizzazione di bozze e prodotti privati nelle selezioni dei "
"prodotti componibili. Utile quando i prodotti devono essere venduti solo "
"come parte di una composizione, non singolarmente."
#: templates/product-selector.twig #: includes/Admin/ProductData.php:126
msgid "Out of stock" msgid "By Category"
msgstr "Esaurito" msgstr "Per categoria"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:128
msgid "Only" msgid "By SKU"
msgstr "Solo" msgstr "Per codice articolo"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:127
msgid "left" msgid "By Tag"
msgstr "rimasti" msgstr "Per etichetta"
#: templates/product-selector.twig #: templates/product-selector.html.twig
msgid "Choose up to"
msgstr "Scegli fino a"
#: includes/Admin/ProductData.php:34
msgid "Composable Options"
msgstr "Opzioni di composizione"
#: includes/Plugin.php:132
msgid "Composable product"
msgstr "Prodotto componibile"
#: includes/Admin/Settings.php:21
msgid "Composable Products"
msgstr "Prodotti Componibili"
#: includes/Admin/Settings.php:34
msgid "Composable Products Settings"
msgstr "Impostazioni prodotti componibili"
#: includes/Admin/Settings.php:36
msgid "Configure default settings for composable products."
msgstr "Configurare le impostazioni predefinite per i prodotti componibili."
#: includes/Admin/Settings.php:52
msgid "Default Pricing Mode"
msgstr "Modalità di prezzo predefinita"
#: includes/Admin/Settings.php:40
msgid "Default Selection Limit"
msgstr "Limite di selezione predefinito"
#: includes/Admin/Settings.php:41
msgid "Default number of items customers can select."
msgstr ""
"Numero predefinito di articoli che i clienti possono selezionare (p. es. 5 "
"adesivi per CHF 50.-)."
#: includes/Admin/Settings.php:79
msgid "Display individual product prices in the selection interface."
msgstr ""
"Visualizzare i prezzi individuali dei prodotti nell'interfaccia di selezione."
#: includes/Admin/Settings.php:72
msgid "Display product images in the selection interface."
msgstr "Visualizzare le immagini dei prodotti nell'interfaccia di selezione."
#: includes/Admin/Settings.php:86
msgid "Display the total price as customers make selections."
msgstr ""
"Visualizzare il prezzo totale mentre i clienti effettuano le selezioni."
#: includes/Admin/ProductData.php:196
msgid "Enter product SKUs separated by commas."
msgstr "Inserire i codici articolo dei prodotti separati da virgole."
#: includes/Admin/ProductData.php:83
msgid "Enter the fixed price for this composable product."
msgstr "Inserisci il prezzo fisso per questo prodotto componibile."
#: includes/Admin/ProductData.php:82
msgid "Fixed Price"
msgstr "Prezzo fisso"
#: includes/Admin/ProductData.php:74 includes/Admin/Settings.php:59
msgid "Fixed price"
msgstr "Prezzo fisso"
#: includes/Admin/Settings.php:53
msgid "How to calculate the price of composable products."
msgstr ""
"Come calcolare il prezzo dei prodotti componibili (p. es. prezzo fisso CHF "
"50.- o somma)."
#: includes/Admin/ProductData.php:69
msgid "How to calculate the price."
msgstr "Come calcolare il prezzo."
#: includes/Admin/ProductData.php:123
msgid "How to select available products."
msgstr "Come selezionare i prodotti disponibili."
#: templates/product-selector.html.twig
msgid "In stock" msgid "In stock"
msgstr "Disponibile" msgstr "Disponibile"
#: templates/product-selector.twig #: includes/Admin/ProductData.php:107 includes/Admin/Settings.php:64
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr "Nessun prodotto disponibile per la selezione. Si prega di configurare i criteri del prodotto nel pannello di amministrazione."
#: includes/Admin/Settings.php, includes/Admin/ProductData.php
msgid "Include Non-Public Products" msgid "Include Non-Public Products"
msgstr "Includi prodotti non pubblici" msgstr "Includi prodotti non pubblici"
#: includes/Admin/Settings.php #: templates/product-selector.html.twig
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually." msgid "items from the selection below."
msgstr "Consenti la visualizzazione di bozze e prodotti privati nelle selezioni dei prodotti componibili. Utile quando i prodotti devono essere venduti solo come parte di una composizione, non singolarmente." msgstr "articoli dalla selezione qui sotto."
#: includes/Admin/ProductData.php #: includes/Plugin.php:178
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition." msgid "Maximum items selected"
msgstr "Consenti bozze e prodotti privati nella selezione. Utile quando i prodotti devono essere venduti solo come parte di una composizione." msgstr ""
#: includes/Admin/ProductData.php #: includes/Admin/ProductData.php:55
msgid ""
"Maximum number of items customers can select. Leave empty to use global "
"default."
msgstr ""
"Numero massimo di articoli che i clienti possono selezionare. Lasciare vuoto "
"per utilizzare il valore predefinito globale."
#: includes/Admin/ProductData.php:113
msgid "No"
msgstr "No"
#: templates/product-selector.html.twig
msgid ""
"No products available for selection. Please configure the product criteria "
"in the admin panel."
msgstr ""
"Nessun prodotto disponibile per la selezione. Si prega di configurare i "
"criteri del prodotto nel pannello di amministrazione."
#: includes/CartHandler.php:115
msgid "One or more selected products are not available."
msgstr "Uno o più prodotti selezionati non sono disponibili."
#: templates/product-selector.html.twig
msgid "Only"
msgstr "Solo"
#. translators: 1: product name, 2: stock quantity
#: includes/StockManager.php:69
#, php-format
msgid "Only %2$d of \"%1$s\" are available in stock."
msgstr "Solo %2$d di \"%1$s\" sono disponibili in magazzino."
#: templates/product-selector.html.twig
msgid "Out of stock"
msgstr "Esaurito"
#: includes/CartHandler.php:84 includes/CartHandler.php:100
msgid "Please select at least one product."
msgstr "Seleziona almeno un prodotto."
#: includes/Plugin.php:179
msgid "Please select at least one item"
msgstr ""
#: includes/Plugin.php:177
msgid "Please select items"
msgstr ""
#: includes/Admin/ProductData.php:68
msgid "Pricing Mode"
msgstr "Modalità di prezzo"
#: includes/Admin/ProductData.php:195
msgid "Product SKUs"
msgstr "Codici articolo prodotti"
#: includes/Admin/ProductData.php:198
msgid "SKU-1, SKU-2, SKU-3"
msgstr "COD-1, COD-2, COD-3"
#: templates/product-selector.html.twig
msgid "Select Your Products"
msgstr "Seleziona i tuoi prodotti"
#: includes/Admin/ProductData.php:138
msgid "Select Categories"
msgstr "Seleziona categorie"
#: includes/Admin/ProductData.php:165
msgid "Select Tags"
msgstr "Seleziona etichette"
#: includes/Admin/ProductData.php:159
msgid "Select product categories to include."
msgstr "Selezionare le categorie di prodotti da includere."
#: includes/Admin/ProductData.php:186
msgid "Select product tags to include."
msgstr "Selezionare le etichette dei prodotti da includere."
#: includes/CartHandler.php:191
msgid "Selected Products"
msgstr "Prodotti selezionati"
#: includes/Admin/ProductData.php:122
msgid "Selection Criteria"
msgstr "Criteri di selezione"
#: includes/Admin/ProductData.php:54
msgid "Selection Limit"
msgstr "Limite di selezione"
#: includes/Admin/Settings.php:71
msgid "Show Product Images"
msgstr "Mostra immagini prodotti"
#: includes/Admin/Settings.php:78
msgid "Show Product Prices"
msgstr "Mostra prezzi prodotti"
#: includes/Admin/Settings.php:85
msgid "Show Total Price"
msgstr "Mostra prezzo totale"
#. translators: 1: product name, 2: quantity, 3: remaining stock
#: includes/StockManager.php:168
#, php-format
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr "Giacenza ridotta per \"%1$s\": -%2$d (rimanenti: %3$d)"
#. translators: 1: product name, 2: quantity, 3: new stock
#: includes/StockManager.php:235
#, php-format
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr "Giacenza ripristinata per \"%1$s\": +%2$d (totale: %3$d)"
#: includes/Admin/ProductData.php:73 includes/Admin/Settings.php:58
msgid "Sum of selected products"
msgstr "Somma dei prodotti selezionati"
#: templates/product-selector.html.twig
msgid "Total Price:"
msgstr "Prezzo totale:"
#: includes/Admin/ProductData.php:72 includes/Admin/ProductData.php:111
msgid "Use global default"
msgstr "Usa predefinito globale"
#: includes/Admin/ProductData.php:112
msgid "Yes" msgid "Yes"
msgstr "Sì" msgstr "Sì"
#: includes/Admin/ProductData.php #. translators: %d: selection limit
msgid "No" #: includes/CartHandler.php:95
msgstr "No" #, php-format
msgid "You can select a maximum of %d products."
msgstr "Puoi selezionare un massimo di %d prodotti."
#: templates/product-selector.html.twig
msgid "left"
msgstr "rimasti"
+250 -229
View File
@@ -2,9 +2,9 @@
# This file is distributed under the GPL v3 or later. # This file is distributed under the GPL v3 or later.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Composable Products 1.1.6\n" "Project-Id-Version: WooCommerce Composable Products 1.3.2\n"
"Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n" "Report-Msgid-Bugs-To: https://github.com/magdev/wc-composable-product/issues\n"
"POT-Creation-Date: 2024-12-31 00:00+0000\n" "POT-Creation-Date: 2026-03-01 13:47+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -12,252 +12,273 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: WP-CLI\n"
#: wc-composable-product.php #: wc-composable-product.php:44
msgid "WooCommerce Composable Products requires WooCommerce to be installed and active." msgid "WooCommerce Composable Products requires WooCommerce to be installed and active."
msgstr "" msgstr ""
#: wc-composable-product.php #: wc-composable-product.php:90
msgid "This plugin requires WooCommerce to be installed and active." msgid "This plugin requires WooCommerce to be installed and active."
msgstr "" msgstr ""
#: wc-composable-product.php #: wc-composable-product.php:91
msgid "Plugin Activation Error" msgid "Plugin Activation Error"
msgstr "" msgstr ""
#: includes/Admin/Settings.php #. translators: %s: product name
msgid "Composable Products" #: includes/StockManager.php:60
msgstr "" #, php-format
#: includes/Admin/Settings.php
msgid "Composable Products Settings"
msgstr ""
#: includes/Admin/Settings.php
msgid "Configure default settings for composable products."
msgstr ""
#: includes/Admin/Settings.php
msgid "Default Selection Limit"
msgstr ""
#: includes/Admin/Settings.php
msgid "Default number of items customers can select."
msgstr ""
#: includes/Admin/Settings.php
msgid "Default Pricing Mode"
msgstr ""
#: includes/Admin/Settings.php
msgid "How to calculate the price of composable products."
msgstr ""
#: includes/Admin/Settings.php
msgid "Sum of selected products"
msgstr ""
#: includes/Admin/Settings.php
msgid "Fixed price"
msgstr ""
#: includes/Admin/Settings.php
msgid "Show Product Images"
msgstr ""
#: includes/Admin/Settings.php
msgid "Display product images in the selection interface."
msgstr ""
#: includes/Admin/Settings.php
msgid "Show Product Prices"
msgstr ""
#: includes/Admin/Settings.php
msgid "Display individual product prices in the selection interface."
msgstr ""
#: includes/Admin/Settings.php
msgid "Show Total Price"
msgstr ""
#: includes/Admin/Settings.php
msgid "Display the total price as customers make selections."
msgstr ""
#: includes/Plugin.php
msgid "Composable product"
msgstr ""
#: includes/ProductSelector.php, templates/product-selector.twig
msgid "Select Your Products"
msgstr ""
#: templates/product-selector.twig
msgid "Choose up to"
msgstr ""
#: templates/product-selector.twig
msgid "items from the selection below."
msgstr ""
#: templates/product-selector.twig
msgid "Total Price:"
msgstr ""
#: templates/product-selector.twig
msgid "Add to Cart"
msgstr ""
#: includes/CartHandler.php
msgid "Please select at least one product."
msgstr ""
#: includes/CartHandler.php
msgid "You can select a maximum of %d products."
msgstr ""
#: includes/CartHandler.php
msgid "One or more selected products are not available."
msgstr ""
#: includes/CartHandler.php
msgid "Selected Products"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Composable Options"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Selection Limit"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr ""
#: includes/Admin/ProductData.php
msgid "Pricing Mode"
msgstr ""
#: includes/Admin/ProductData.php
msgid "How to calculate the price."
msgstr ""
#: includes/Admin/ProductData.php
msgid "Fixed Price"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Enter the fixed price for this composable product."
msgstr ""
#: includes/Admin/ProductData.php
msgid "Use global default"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Selection Criteria"
msgstr ""
#: includes/Admin/ProductData.php
msgid "How to select available products."
msgstr ""
#: includes/Admin/ProductData.php
msgid "By Category"
msgstr ""
#: includes/Admin/ProductData.php
msgid "By Tag"
msgstr ""
#: includes/Admin/ProductData.php
msgid "By SKU"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Select Categories"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Select product categories to include."
msgstr ""
#: includes/Admin/ProductData.php
msgid "Select Tags"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Select product tags to include."
msgstr ""
#: includes/Admin/ProductData.php
msgid "Product SKUs"
msgstr ""
#: includes/Admin/ProductData.php
msgid "Enter product SKUs separated by commas."
msgstr ""
#: includes/Admin/ProductData.php
msgid "SKU-1, SKU-2, SKU-3"
msgstr ""
#: includes/StockManager.php
msgid "\"%s\" is out of stock and cannot be selected." msgid "\"%s\" is out of stock and cannot be selected."
msgstr "" msgstr ""
#: includes/StockManager.php #: templates/product-selector.html.twig
msgid "Only %2$d of \"%1$s\" are available in stock." msgid "Add to Cart"
msgstr "" msgstr ""
#: includes/StockManager.php #: includes/Admin/ProductData.php:108
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr ""
#: includes/StockManager.php
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr ""
#: templates/product-selector.twig
msgid "Out of stock"
msgstr ""
#: templates/product-selector.twig
msgid "Only"
msgstr ""
#: templates/product-selector.twig
msgid "left"
msgstr ""
#: templates/product-selector.twig
msgid "In stock"
msgstr ""
#: templates/product-selector.twig
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr ""
#: includes/Admin/Settings.php, includes/Admin/ProductData.php
msgid "Include Non-Public Products"
msgstr ""
#: includes/Admin/Settings.php
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr ""
#: includes/Admin/ProductData.php
msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition." msgid "Allow draft and private products in the selection. Useful when products should only be sold as part of a composition."
msgstr "" msgstr ""
#: includes/Admin/ProductData.php #: includes/Admin/Settings.php:65
msgid "Allow draft and private products to appear in composable product selections. Useful when products should only be sold as part of a composition, not individually."
msgstr ""
#: includes/Admin/ProductData.php:126
msgid "By Category"
msgstr ""
#: includes/Admin/ProductData.php:128
msgid "By SKU"
msgstr ""
#: includes/Admin/ProductData.php:127
msgid "By Tag"
msgstr ""
#: templates/product-selector.html.twig
msgid "Choose up to"
msgstr ""
#: includes/Admin/ProductData.php:34
msgid "Composable Options"
msgstr ""
#: includes/Plugin.php:132
msgid "Composable product"
msgstr ""
#: includes/Admin/Settings.php:21
msgid "Composable Products"
msgstr ""
#: includes/Admin/Settings.php:34
msgid "Composable Products Settings"
msgstr ""
#: includes/Admin/Settings.php:36
msgid "Configure default settings for composable products."
msgstr ""
#: includes/Admin/Settings.php:52
msgid "Default Pricing Mode"
msgstr ""
#: includes/Admin/Settings.php:40
msgid "Default Selection Limit"
msgstr ""
#: includes/Admin/Settings.php:41
msgid "Default number of items customers can select."
msgstr ""
#: includes/Admin/Settings.php:79
msgid "Display individual product prices in the selection interface."
msgstr ""
#: includes/Admin/Settings.php:72
msgid "Display product images in the selection interface."
msgstr ""
#: includes/Admin/Settings.php:86
msgid "Display the total price as customers make selections."
msgstr ""
#: includes/Admin/ProductData.php:196
msgid "Enter product SKUs separated by commas."
msgstr ""
#: includes/Admin/ProductData.php:83
msgid "Enter the fixed price for this composable product."
msgstr ""
#: includes/Admin/ProductData.php:82
msgid "Fixed Price"
msgstr ""
#: includes/Admin/ProductData.php:74 includes/Admin/Settings.php:59
msgid "Fixed price"
msgstr ""
#: includes/Admin/Settings.php:53
msgid "How to calculate the price of composable products."
msgstr ""
#: includes/Admin/ProductData.php:69
msgid "How to calculate the price."
msgstr ""
#: includes/Admin/ProductData.php:123
msgid "How to select available products."
msgstr ""
#: templates/product-selector.html.twig
msgid "In stock"
msgstr ""
#: includes/Admin/ProductData.php:107 includes/Admin/Settings.php:64
msgid "Include Non-Public Products"
msgstr ""
#: templates/product-selector.html.twig
msgid "items from the selection below."
msgstr ""
#: includes/Plugin.php:178
msgid "Maximum items selected"
msgstr ""
#: includes/Admin/ProductData.php:55
msgid "Maximum number of items customers can select. Leave empty to use global default."
msgstr ""
#: includes/Admin/ProductData.php:113
msgid "No"
msgstr ""
#: templates/product-selector.html.twig
msgid "No products available for selection. Please configure the product criteria in the admin panel."
msgstr ""
#: includes/CartHandler.php:115
msgid "One or more selected products are not available."
msgstr ""
#: templates/product-selector.html.twig
msgid "Only"
msgstr ""
#. translators: 1: product name, 2: stock quantity
#: includes/StockManager.php:69
#, php-format
msgid "Only %2$d of \"%1$s\" are available in stock."
msgstr ""
#: templates/product-selector.html.twig
msgid "Out of stock"
msgstr ""
#: includes/CartHandler.php:84 includes/CartHandler.php:100
msgid "Please select at least one product."
msgstr ""
#: includes/Plugin.php:179
msgid "Please select at least one item"
msgstr ""
#: includes/Plugin.php:177
msgid "Please select items"
msgstr ""
#: includes/Admin/ProductData.php:68
msgid "Pricing Mode"
msgstr ""
#: includes/Admin/ProductData.php:195
msgid "Product SKUs"
msgstr ""
#: includes/Admin/ProductData.php:198
msgid "SKU-1, SKU-2, SKU-3"
msgstr ""
#: templates/product-selector.html.twig
msgid "Select Your Products"
msgstr ""
#: includes/Admin/ProductData.php:138
msgid "Select Categories"
msgstr ""
#: includes/Admin/ProductData.php:165
msgid "Select Tags"
msgstr ""
#: includes/Admin/ProductData.php:159
msgid "Select product categories to include."
msgstr ""
#: includes/Admin/ProductData.php:186
msgid "Select product tags to include."
msgstr ""
#: includes/CartHandler.php:191
msgid "Selected Products"
msgstr ""
#: includes/Admin/ProductData.php:122
msgid "Selection Criteria"
msgstr ""
#: includes/Admin/ProductData.php:54
msgid "Selection Limit"
msgstr ""
#: includes/Admin/Settings.php:71
msgid "Show Product Images"
msgstr ""
#: includes/Admin/Settings.php:78
msgid "Show Product Prices"
msgstr ""
#: includes/Admin/Settings.php:85
msgid "Show Total Price"
msgstr ""
#. translators: 1: product name, 2: quantity, 3: remaining stock
#: includes/StockManager.php:168
#, php-format
msgid "Stock reduced for \"%1$s\": -%2$d (remaining: %3$d)"
msgstr ""
#. translators: 1: product name, 2: quantity, 3: new stock
#: includes/StockManager.php:235
#, php-format
msgid "Stock restored for \"%1$s\": +%2$d (total: %3$d)"
msgstr ""
#: includes/Admin/ProductData.php:73 includes/Admin/Settings.php:58
msgid "Sum of selected products"
msgstr ""
#: templates/product-selector.html.twig
msgid "Total Price:"
msgstr ""
#: includes/Admin/ProductData.php:72 includes/Admin/ProductData.php:111
msgid "Use global default"
msgstr ""
#: includes/Admin/ProductData.php:112
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""
#: includes/Admin/ProductData.php #. translators: %d: selection limit
msgid "No" #: includes/CartHandler.php:95
#, php-format
msgid "You can select a maximum of %d products."
msgstr ""
#: templates/product-selector.html.twig
msgid "left"
msgstr "" msgstr ""
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<ruleset name="WC Composable Product">
<description>PHPCS rules for WooCommerce Composable Products plugin</description>
<!-- Scan plugin source -->
<file>includes</file>
<file>wc-composable-product.php</file>
<!-- Show progress and use colors -->
<arg value="ps"/>
<arg name="colors"/>
<!-- Use WordPress Extra standard (Core + Extra, without Docs) -->
<rule ref="WordPress-Extra">
<!-- Allow PSR-4 PascalCase file naming -->
<exclude name="WordPress.Files.FileName"/>
</rule>
<!-- Check PHP 8.3+ compatibility -->
<rule ref="PHPCompatibilityWP"/>
<config name="testVersion" value="8.3-"/>
<!-- WordPress minimum version -->
<config name="minimum_wp_version" value="6.0"/>
<!-- Enforce text domain -->
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="wc-composable-product"/>
</property>
</properties>
</rule>
</ruleset>
+4 -7
View File
@@ -1,14 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit <phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
bootstrap="tests/bootstrap.php" bootstrap="tests/bootstrap.php"
colors="true" colors="true"
verbose="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="false"
failOnWarning="true" failOnWarning="true"
cacheDirectory=".phpunit.cache"
> >
<testsuites> <testsuites>
<testsuite name="Unit"> <testsuite name="Unit">
@@ -16,9 +13,9 @@
</testsuite> </testsuite>
</testsuites> </testsuites>
<coverage processUncoveredFiles="true"> <source>
<include> <include>
<directory suffix=".php">includes</directory> <directory suffix=".php">includes</directory>
</include> </include>
</coverage> </source>
</phpunit> </phpunit>
@@ -0,0 +1,83 @@
<?php
/**
* Custom single product template for composable products.
*
* Replaces the standard WooCommerce two-column layout (image + summary) with
* a compact header and full-width product selector.
*
* This is a thin PHP loader that captures WooCommerce hook output and passes
* it to the Twig template for rendering.
*
* @package Magdev\WcComposableProduct
*/
defined( 'ABSPATH' ) || exit;
global $product;
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
return;
}
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_before_single_product' );
if ( post_password_required() ) {
echo get_the_password_form(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP core function.
return;
}
// Temporarily remove standard WooCommerce summary hooks — we render title,
// price, and description in the compact header instead. Our product selector
// (CartHandler::render_product_selector at priority 25) stays attached.
$hooks_to_remove = array(
array( 'woocommerce_template_single_title', 5 ),
array( 'woocommerce_template_single_rating', 10 ),
array( 'woocommerce_template_single_price', 10 ),
array( 'woocommerce_template_single_excerpt', 20 ),
array( 'woocommerce_template_single_add_to_cart', 30 ),
array( 'woocommerce_template_single_meta', 40 ),
array( 'woocommerce_template_single_sharing', 50 ),
);
foreach ( $hooks_to_remove as $hook ) {
remove_action( 'woocommerce_single_product_summary', $hook[0], $hook[1] );
}
// Capture product selector output (our hook at priority 25 + structured data at 60).
ob_start();
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_single_product_summary' );
$selector_html = ob_get_clean();
// Restore removed hooks.
foreach ( $hooks_to_remove as $hook ) {
add_action( 'woocommerce_single_product_summary', $hook[0], $hook[1] );
}
// Capture after-summary output (product tabs, related products, etc.).
ob_start();
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_after_single_product_summary' );
$after_summary_html = ob_get_clean();
// Build template context.
$image_id = $product->get_image_id();
$context = array(
'product_id' => $product->get_id(),
'product_name' => $product->get_name(),
'price_html' => $product->get_price_html(),
'short_description' => $product->get_short_description(),
'image_html' => $image_id ? wp_get_attachment_image( $image_id, array( 100, 100 ), false, array( 'class' => 'composable-header-image' ) ) : '',
'permalink' => get_permalink( $product->get_id() ),
'product_class' => implode( ' ', wc_get_product_class( 'composable-product-layout', $product ) ),
'selector_html' => $selector_html,
'after_summary_html' => $after_summary_html,
);
$plugin = \Magdev\WcComposableProduct\Plugin::instance();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped by Twig template.
echo $plugin->render_template( 'single-product-composable.html.twig', $context );
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_after_single_product' );
File renamed without changes.
@@ -0,0 +1,37 @@
{# Custom single product template for composable products #}
<div id="product-{{ product_id }}" class="{{ product_class }}">
{# Compact product header — replaces the large image gallery #}
<div class="composable-product-header">
{% if image_html %}
<div class="composable-header-thumbnail">
{{ image_html|raw }}
</div>
{% endif %}
<div class="composable-header-info">
<h1 class="product_title entry-title">{{ product_name|esc_html }}</h1>
{% if price_html %}
<div class="composable-header-price price">
{{ price_html|raw }}
</div>
{% endif %}
{% if short_description %}
<div class="composable-header-description">
{{ short_description|raw }}
</div>
{% endif %}
</div>
</div>
{# Full-width product selector #}
<div class="composable-selector-area">
{{ selector_html|raw }}
</div>
{# WooCommerce after-summary content (tabs, related products) #}
{{ after_summary_html|raw }}
</div>
+31 -25
View File
@@ -4,7 +4,7 @@
* Plugin Name: WooCommerce Composable Products * Plugin Name: WooCommerce Composable Products
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-composable-product * Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-composable-product
* Description: Create composable products where customers select a limited number of items from a configurable set * Description: Create composable products where customers select a limited number of items from a configurable set
* Version: 1.3.1 * Version: 1.3.2
* Author: Marco Graetsch * Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev * Author URI: https://src.bundespruefstelle.ch/magdev
* License: GPL v3 or later * License: GPL v3 or later
@@ -17,17 +17,17 @@
* WC tested up to: 10.0 * WC tested up to: 10.0
*/ */
defined('ABSPATH') || exit; defined( 'ABSPATH' ) || exit;
// Define plugin constants // Define plugin constants
define('WC_COMPOSABLE_PRODUCT_VERSION', '1.3.1'); define( 'WC_COMPOSABLE_PRODUCT_VERSION', '1.3.2' );
define('WC_COMPOSABLE_PRODUCT_FILE', __FILE__); define( 'WC_COMPOSABLE_PRODUCT_FILE', __FILE__ );
define('WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path(__FILE__)); define( 'WC_COMPOSABLE_PRODUCT_PATH', plugin_dir_path( __FILE__ ) );
define('WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url(__FILE__)); define( 'WC_COMPOSABLE_PRODUCT_URL', plugin_dir_url( __FILE__ ) );
define('WC_COMPOSABLE_PRODUCT_BASENAME', plugin_basename(__FILE__)); define( 'WC_COMPOSABLE_PRODUCT_BASENAME', plugin_basename( __FILE__ ) );
// Load Composer autoloader // Load Composer autoloader
if (file_exists(WC_COMPOSABLE_PRODUCT_PATH . 'vendor/autoload.php')) { if ( file_exists( WC_COMPOSABLE_PRODUCT_PATH . 'vendor/autoload.php' ) ) {
require_once WC_COMPOSABLE_PRODUCT_PATH . 'vendor/autoload.php'; require_once WC_COMPOSABLE_PRODUCT_PATH . 'vendor/autoload.php';
} }
@@ -35,14 +35,17 @@ if (file_exists(WC_COMPOSABLE_PRODUCT_PATH . 'vendor/autoload.php')) {
* Check if WooCommerce is active * Check if WooCommerce is active
*/ */
function wc_composable_product_check_woocommerce() { function wc_composable_product_check_woocommerce() {
if (!class_exists('WooCommerce')) { if ( ! class_exists( 'WooCommerce' ) ) {
add_action('admin_notices', function() { add_action(
'admin_notices',
function () {
?> ?>
<div class="notice notice-error"> <div class="notice notice-error">
<p><?php esc_html_e('WooCommerce Composable Products requires WooCommerce to be installed and active.', 'wc-composable-product'); ?></p> <p><?php esc_html_e( 'WooCommerce Composable Products requires WooCommerce to be installed and active.', 'wc-composable-product' ); ?></p>
</div> </div>
<?php <?php
}); }
);
return false; return false;
} }
return true; return true;
@@ -52,39 +55,42 @@ function wc_composable_product_check_woocommerce() {
* Initialize the plugin * Initialize the plugin
*/ */
function wc_composable_product_init() { function wc_composable_product_init() {
if (!wc_composable_product_check_woocommerce()) { if ( ! wc_composable_product_check_woocommerce() ) {
return; return;
} }
// Load text domain // Load text domain
load_plugin_textdomain('wc-composable-product', false, dirname(WC_COMPOSABLE_PRODUCT_BASENAME) . '/languages'); load_plugin_textdomain( 'wc-composable-product', false, dirname( WC_COMPOSABLE_PRODUCT_BASENAME ) . '/languages' );
// Initialize main plugin class // Initialize main plugin class
Magdev\WcComposableProduct\Plugin::instance(); Magdev\WcComposableProduct\Plugin::instance();
} }
// Use woocommerce_init to ensure all WooCommerce classes including settings are loaded // Use woocommerce_init to ensure all WooCommerce classes including settings are loaded
add_action('woocommerce_init', 'wc_composable_product_init'); add_action( 'woocommerce_init', 'wc_composable_product_init' );
/** /**
* Declare HPOS compatibility * Declare HPOS compatibility
*/ */
add_action('before_woocommerce_init', function() { add_action(
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) { 'before_woocommerce_init',
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true); function () {
if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
} }
}); }
);
/** /**
* Activation hook * Activation hook
*/ */
function wc_composable_product_activate() { function wc_composable_product_activate() {
if (!class_exists('WooCommerce')) { if ( ! class_exists( 'WooCommerce' ) ) {
deactivate_plugins(WC_COMPOSABLE_PRODUCT_BASENAME); deactivate_plugins( WC_COMPOSABLE_PRODUCT_BASENAME );
wp_die( wp_die(
esc_html__('This plugin requires WooCommerce to be installed and active.', 'wc-composable-product'), esc_html__( 'This plugin requires WooCommerce to be installed and active.', 'wc-composable-product' ),
esc_html__('Plugin Activation Error', 'wc-composable-product'), esc_html__( 'Plugin Activation Error', 'wc-composable-product' ),
array('back_link' => true) array( 'back_link' => true )
); );
} }
} }
register_activation_hook(__FILE__, 'wc_composable_product_activate'); register_activation_hook( __FILE__, 'wc_composable_product_activate' );