diff --git a/.travis.yml b/.travis.yml index f64f7b018..4bc5b86f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,25 +2,23 @@ language: php sudo: false php: - - 5.3 + - 5.5 env: - DB=MYSQL CORE_RELEASE=3.2 matrix: include: - - php: 5.4 - env: DB=MYSQL CORE_RELEASE=3.1 - - php: 5.5 - env: DB=SQLITE CORE_RELEASE=3.2 - php: 5.6 env: DB=PGSQL CORE_RELEASE=3.2 COVERAGE=1 - php: 5.6 env: DB=PGSQL CORE_RELEASE=3.2 COVERAGE=2 - php: 5.6 env: DB=PGSQL CORE_RELEASE=3.2 COVERAGE=3 - - php: 5.6 + - php: 5.6 env: DB=PGSQL CORE_RELEASE=3.3 + - php: 5.5 + env: DB=SQLITE CORE_RELEASE=3.1 - php: 5.6 env: DB=PGSQL CORE_RELEASE=master allow_failures: diff --git a/.tx/config b/.tx/config new file mode 100644 index 000000000..e138a4d39 --- /dev/null +++ b/.tx/config @@ -0,0 +1,8 @@ +[main] +host = https://www.transifex.com + +[silverstripe-shop.lang_2-0] +file_filter = lang/.yml +source_file = lang/en.yml +source_lang = en +type = YML diff --git a/ChangeLog.md b/ChangeLog.md index aba440196..75bd91afd 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,17 @@ # SilverStripe Shop Change Log +## 2.0 + + * Overhaul of the localization of the module. (#379, #381, #383, #384, #408, #410, #411, #451, #453, #456, #457, #470, #486) + * Better Addressbook UI (#452) + * Use `::create` syntax for object instantiation (#415) + * Shop Emails use inline CSS by using Emogrifier (#466) + * Order-calculations and order-placement are now wrapped in Database transactions (#506) + * Products that have Variations can not be added to cart, only a Variation can (#516) + * Products with no price (`0.0`) will now be displayed if they have Variations (#516) + * Variations without a price will no longer show up, unless `Product.allow_zero_price` is set to `true` (#516) + * Updated codebase to use the 2.0 version of silverstripe-omnipay + ## 1.3.1 * Improve variation-form performance (#512) diff --git a/README.md b/README.md index 832123929..ec6e6d6bc 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ A tremendous thanks to [everyone that has already contributed](https://github.co ## Requirements * SilverStripe 3.1 or higher [framework](https://github.com/silverstripe/silverstripe-framework) & [cms](https://github.com/silverstripe/silverstripe-cms) - * [Omnipay Module](https://github.com/burnbright/silverstripe-omnipay) + it's dependencies. + * [Omnipay Module](https://github.com/burnbright/silverstripe-omnipay) + its dependencies. See `composer.json` for exact set of dependencies. @@ -41,7 +41,7 @@ See `composer.json` for exact set of dependencies. To install silverstripe + shop into a directory called 'myshop', using [composer](http://doc.silverstripe.org/framework/en/installation/composer), run the following commands: ``` composer create-project silverstripe/installer myshop -composer require -d myshop "silvershop/core:dev-master" +composer require -d myshop "silvershop/core" ``` ### Build Tasks diff --git a/_config/config.yml b/_config/config.yml index 7341a9fa8..40507ee71 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -20,6 +20,8 @@ OrderProcessor: Injector: CheckoutComponentConfig: class: SinglePageCheckoutComponentConfig + ShopEmail: + class: StyledHtmlEmail LeftAndMain: extra_requirements_css: diff --git a/_config/extensions.yml b/_config/extensions.yml index 3a1a0ab9a..8115d834b 100644 --- a/_config/extensions.yml +++ b/_config/extensions.yml @@ -35,3 +35,6 @@ Payment: Order: extensions: - Payable +SilverStripe\Omnipay\Service\PaymentService: + extensions: + - ShopPaymentService diff --git a/code/ShopTools.php b/code/ShopTools.php index 5e42e4fbf..0293c9227 100644 --- a/code/ShopTools.php +++ b/code/ShopTools.php @@ -5,6 +5,24 @@ */ class ShopTools { + /** + * Get the DB connection in a SS 3.1 and 3.2+ compatible way + * @param string $name + * @return SS_Database + */ + public static function DBConn($name = 'default') + { + if (method_exists('DB', 'get_conn')) { + return DB::get_conn($name); + } + return DB::getConn($name); + } + + /** + * Convert a numeric price to the shop currency + * @param mixed $price the price to convert + * @return Money the price wrapped in a Money DBField to be used for templates or similar + */ public static function price_for_display($price) { $currency = ShopConfig::get_site_currency(); @@ -14,6 +32,11 @@ public static function price_for_display($price) return $field; } + /** + * Get the current locale. + * Tries to get the locale from Translatable, Fluent or the default i18n (depending on what is installed) + * @return string the locale in use + */ public static function get_current_locale() { if (class_exists('Translatable')) { @@ -27,6 +50,12 @@ public static function get_current_locale() return i18n::get_locale(); } + /** + * Set/Install the given locale. + * This does set the i18n locale as well as the Translatable or Fluent locale (if any of these modules is installed) + * @param string $locale the locale to install + * @throws Zend_Locale_Exception @see Zend_Locale_Format::getDateFormat and @see Zend_Locale_Format::getTimeFormat + */ public static function install_locale($locale) { // If the locale isn't given, silently fail (there might be carts that still have locale set to null) diff --git a/code/account/AccountPage.php b/code/account/AccountPage.php index 25a27df47..b1cdec431 100644 --- a/code/account/AccountPage.php +++ b/code/account/AccountPage.php @@ -19,6 +19,8 @@ public function canCreate($member = null, $context = array()) * Returns the link or the URLSegment to the account page on this site * * @param boolean $urlSegment Return the URLSegment only + * + * @return mixed */ public static function find_link($urlSegment = false) { @@ -31,6 +33,8 @@ public static function find_link($urlSegment = false) * * @param int|string $orderID ID of the order * @param boolean $urlSegment Return the URLSegment only + * + * @return string */ public static function get_order_link($orderID, $urlSegment = false) { @@ -38,12 +42,16 @@ public static function get_order_link($orderID, $urlSegment = false) return ($urlSegment ? $page->URLSegment . '/' : $page->Link()) . 'order/' . $orderID; } + /** + * @return AccountPage + */ protected static function get_if_account_page_exists() { if ($page = DataObject::get_one('AccountPage')) { return $page; } - user_error(_t('AccountPage.NO_PAGE', 'No AccountPage was found. Please create one in the CMS!'), E_USER_ERROR); + user_error(_t('AccountPage.NoPage', 'No AccountPage was found. Please create one in the CMS!'), E_USER_ERROR); + return null; // just to keep static analysis happy } /** @@ -53,6 +61,7 @@ public function requireDefaultRecords() { parent::requireDefaultRecords(); if (!self::get()->exists() && $this->config()->create_default_pages) { + /** @var AccountPage $page */ $page = self::create( array( 'Title' => 'Account', @@ -80,9 +89,13 @@ class AccountPage_Controller extends Page_Controller 'EditAccountForm', 'ChangePasswordForm', 'changepassword', // redirects to editprofile + 'deleteaddress', + 'setdefaultbilling', + 'setdefaultshipping', ); - protected $member; + /** @var Member|ShopMember */ + protected $member; public function init() { @@ -90,21 +103,21 @@ public function init() if (!Member::currentUserID()) { $messages = array( 'default' => _t( - 'AccountPage.LOGIN', + 'AccountPage.Login', 'You\'ll need to login before you can access the account page. If you are not registered, you won\'t be able to access it until you make your first order, otherwise please enter your details below.' ), 'logInAgain' => _t( - 'AccountPage.LOGINAGAIN', + 'AccountPage.LoginAgain', 'You have been logged out. If you would like to log in again, please do so below.' ), ); Security::permissionFailure($this, $messages); - return false; + } else { + $this->member = Member::currentUser(); } - $this->member = Member::currentUser(); } public function getTitle() @@ -112,7 +125,7 @@ public function getTitle() if ($this->dataRecord && $title = $this->dataRecord->Title) { return $title; } - return _t('AccountPage.Title', "Account"); + return _t('AccountPage.DefaultTitle', "Account"); } public function getMember() @@ -201,7 +214,7 @@ public function saveaddress($data, $form) $member->DefaultBillingAddressID = $address->ID; $member->write(); } - $form->sessionMessage(_t("CreateAddressForm.SAVED", "Your address has been saved"), "good"); + $form->sessionMessage(_t("CreateAddressForm.AddressSaved", "Your address has been saved"), "good"); $this->extend('updateCreateAddressFormResponse', $form, $data, $response); @@ -213,6 +226,47 @@ public function editprofile() return array(); } + /** + * @param SS_HTTPRequest $req + * @return SS_HTTPResponse + */ + function deleteaddress($req) + { + // NOTE: we don't want to fully delete the address because it's presumably still + // attached to an order. Setting MemberID to 0 means it won't show up in the address + // book any longer. + $address = $this->member->AddressBook()->byID($req->param('ID')); + if ($address) { + $address->MemberID = 0; + $address->write(); + } else { + $this->httpError(404, 'Address not found'); + } + return $this->redirectBack(); + } + + /** + * @param SS_HTTPRequest $req + * @return SS_HTTPResponse + */ + function setdefaultbilling($req) + { + $this->member->DefaultBillingAddressID = $req->param('ID'); + $this->member->write(); + return $this->redirectBack(); + } + + /** + * @param SS_HTTPRequest $req + * @return SS_HTTPResponse + */ + function setdefaultshipping($req) + { + $this->member->DefaultShippingAddressID = $req->param('ID'); + $this->member->write(); + return $this->redirectBack(); + } + /** * Return a form allowing the user to edit their details. * @@ -237,14 +291,6 @@ public function ChangePasswordForm() $backURL->setValue($this->Link('editprofile')); $this->extend('updateChangePasswordForm', $form); - $this->data()->extend('updateChangePasswordForm', $form); - - if ($this->data()->hasMethod('updateChangePasswordForm')) { // if accessing through the model - Deprecation::notice( - '2.0', - 'Please access updateChangePasswordForm through AccountPage_Controller instead of AccountPage (this extension point is due to be removed)' - ); - } return $form; } diff --git a/code/account/OrderActionsForm.php b/code/account/OrderActionsForm.php index a5e2cc4a0..756bfb9d9 100644 --- a/code/account/OrderActionsForm.php +++ b/code/account/OrderActionsForm.php @@ -1,5 +1,8 @@ allow_paying && $order->canPay()) { - $gateways = GatewayInfo::get_supported_gateways(); + $gateways = GatewayInfo::getSupportedGateways(); //remove manual gateways foreach ($gateways as $gateway => $gatewayname) { - if (GatewayInfo::is_manual($gateway)) { + if (GatewayInfo::isManual($gateway)) { unset($gateways[$gateway]); } } @@ -42,33 +47,43 @@ public function __construct($controller, $name, Order $order) $fields->push( HeaderField::create( "MakePaymentHeader", - _t("OrderActionsForm.MAKEPAYMENT", "Make Payment") + _t("OrderActionsForm.MakePayment", "Make Payment") ) ); $outstandingfield = Currency::create(); - $outstandingfield->setValue($order->TotalOutstanding()); + $outstandingfield->setValue($order->TotalOutstanding(true)); $fields->push( LiteralField::create( "Outstanding", - sprintf( - _t("OrderActionsForm.OUTSTANDING", "Outstanding: %s"), - $outstandingfield->Nice() + _t( + 'Order.OutstandingWithAmount', + 'Outstanding: {Amount}', + '', + array('Amount' => $outstandingfield->Nice()) ) ) ); $fields->push( OptionsetField::create( 'PaymentMethod', - _t("OrderActionsForm.PAYMENTMETHOD", "Payment Method"), + _t("OrderActionsForm.PaymentMethod", "Payment Method"), $gateways, key($gateways) ) ); + if ($ccFields = $this->getCCFields($gateways)) { + if ($this->config()->include_jquery) { + Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.min.js'); + } + Requirements::javascript(SHOP_DIR . '/javascript/OrderActionsForm.js'); + $fields->push($ccFields); + } + $actions->push( FormAction::create( 'dopayment', - _t('OrderActionsForm.PAYORDER', 'Pay outstanding balance') + _t('OrderActionsForm.PayOrder', 'Pay outstanding balance') ) ); } @@ -78,11 +93,13 @@ public function __construct($controller, $name, Order $order) $actions->push( FormAction::create( 'docancel', - _t('OrderActionsForm.CANCELORDER', 'Cancel this order') + _t('OrderActionsForm.CancelOrder', 'Cancel this order') ) ); } - parent::__construct($controller, $name, $fields, $actions); + parent::__construct($controller, $name, $fields, $actions, OrderActionsForm_Validator::create(array( + 'PaymentMethod' + ))); $this->extend("updateForm", $order); } @@ -104,27 +121,23 @@ public function dopayment($data, $form) $data = $form->getData(); $gateway = (!empty($data['PaymentMethod'])) ? $data['PaymentMethod'] : null; - if (!GatewayInfo::is_manual($gateway)) { + if (!GatewayInfo::isManual($gateway)) { + /** @var OrderProcessor $processor */ $processor = OrderProcessor::create($this->order); - $data['cancelUrl'] = $processor->getReturnUrl(); - $response = $processor->makePayment($gateway, $data); - - if ($response) { - if ($response->isRedirect() || $response->isSuccessful()) { - return $response->redirect(); - } - $form->sessionMessage($response->getMessage(), 'bad'); + $response = $processor->makePayment($gateway, $data, $processor->getReturnUrl()); + if($response && !$response->isError()){ + return $response->redirectOrRespond(); } else { $form->sessionMessage($processor->getError(), 'bad'); } } else { - $form->sessionMessage(_t('OrderActionsForm.MANUAL_NOT_ALLOWED', "Manual payment not allowed"), 'bad'); + $form->sessionMessage(_t('OrderActionsForm.ManualNotAllowed', "Manual payment not allowed"), 'bad'); } return $this->controller->redirectBack(); } $form->sessionMessage( - _t('OrderForm.COULDNOTPROCESSPAYMENT', 'Payment could not be processed.'), + _t('OrderForm.CouldNotProcessPayment', 'Payment could not be processed.'), 'bad' ); $this->controller->redirectBack(); @@ -147,20 +160,13 @@ public function docancel($data, $form) ) { $this->order->Status = 'MemberCancelled'; $this->order->write(); + if (self::config()->email_notification) { - $email = Email::create( - Email::config()->admin_email, - Email::config()->admin_email, - sprintf( - _t('Order.CANCELSUBJECT', 'Order #%d cancelled by member'), - $this->order->ID - ), - $this->order->renderWith('Order') - ); - $email->send(); + OrderEmailNotifier::create($this->order)->sendCancelNotification(); } + $this->controller->sessionMessage( - _t("OrderForm.ORDERCANCELLED", "Order sucessfully cancelled"), + _t("OrderForm.OrderCancelled", "Order sucessfully cancelled"), 'warning' ); if (Member::currentUser() && $link = $this->order->Link()) { @@ -170,4 +176,81 @@ public function docancel($data, $form) } } } + + /** + * Get credit card fields for the given gateways + * @param array $gateways + * @return CompositeField|null + */ + protected function getCCFields(array $gateways) + { + $onsiteGateways = array(); + $allRequired = array(); + foreach ($gateways as $gateway => $title) { + if (!GatewayInfo::isOffsite($gateway)) { + $required = GatewayInfo::requiredFields($gateway); + $onsiteGateways[$gateway] = $required; + $allRequired += $required; + } + } + + $allRequired = array_unique($allRequired); + + if (empty($onsiteGateways)) { + return null; + } + + $factory = new GatewayFieldsFactory(null, array('Card')); + $ccFields = $factory->getCardFields(); + + // Remove all the credit card fields that aren't required by any gateway + foreach ($ccFields->dataFields() as $name => $field) { + if ($name && !in_array($name, $allRequired)) { + $ccFields->removeByName($name, true); + } + } + + $lookupField = LiteralField::create( + '_CCLookupField', + sprintf( + '', + json_encode($onsiteGateways, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) + ) + ); + + $ccFields->push($lookupField); + + return CompositeField::create($ccFields)->setTag('fieldset')->addExtraClass('credit-card'); + } +} + +class OrderActionsForm_Validator extends RequiredFields +{ + public function php($data) + { + // Check if we should do a payment + if (Form::current_action() == 'dopayment' && !empty($data['PaymentMethod'])) { + $gateway = $data['PaymentMethod']; + // If the gateway isn't manual and not offsite, Check for credit-card fields! + if (!GatewayInfo::isManual($gateway) && !GatewayInfo::isOffsite($gateway)) { + // Merge the required fields and the Credit-Card fields that are required for the gateway + $this->required = array_merge($this->required, array_intersect( + array( + 'type', + 'name', + 'number', + 'startMonth', + 'startYear', + 'expiryMonth', + 'expiryYear', + 'cvv', + 'issueNumber' + ), + GatewayInfo::requiredFields($gateway) + )); + } + } + + return parent::php($data); + } } diff --git a/code/account/ShopAccountForm.php b/code/account/ShopAccountForm.php index dc741c6c5..4d51486e0 100644 --- a/code/account/ShopAccountForm.php +++ b/code/account/ShopAccountForm.php @@ -15,38 +15,25 @@ public function __construct($controller, $name) if ($member && $member->exists()) { $fields = $member->getMemberFormFields(); $fields->removeByName('Password'); - $requiredFields = $member->getValidator(); + //TODO: This can be reverted to be $member->getValidator() as soon as this fix lands in framework + // (most likely 3.4) https://github.com/silverstripe/silverstripe-framework/pull/5098 + $requiredFields = ShopAccountFormValidator::create(); $requiredFields->addRequiredField('Surname'); } else { $fields = FieldList::create(); } if (get_class($controller) == 'AccountPage_Controller') { - $actions = FieldList::create(FormAction::create('submit', _t('MemberForm.SAVE', 'Save Changes'))); + $actions = FieldList::create(FormAction::create('submit', _t('MemberForm.Save', 'Save Changes'))); } else { $actions = FieldList::create( - FormAction::create('submit', _t('MemberForm.SAVE', 'Save Changes')), - FormAction::create('proceed', _t('MemberForm.SAVEANDPROCEED', 'Save and proceed to checkout')) + FormAction::create('submit', _t('MemberForm.Save', 'Save Changes')), + FormAction::create('proceed', _t('MemberForm.SaveAndProceed', 'Save and proceed to checkout')) ); } parent::__construct($controller, $name, $fields, $actions, $requiredFields); $this->extend('updateShopAccountForm'); - if ($record = $controller->data()) { - $record->extend('updateShopAccountForm', $fields, $actions, $requiredFields); - } - - if ($controller->data() - && $controller->data()->hasMethod( - 'updateShopAccountForm' - ) - ) { // if accessing through the model - Deprecation::notice( - '2.0', - 'Please access updateShopAccountForm through ShopAccountForm instead of AccountPage (this extension point is due to be removed)' - ); - } - if ($member) { $member->Password = ""; //prevents password field from being populated with encrypted password data $this->loadDataFrom($member); @@ -71,7 +58,7 @@ public function submit($data, $form, $request) $form->saveInto($member); $member->write(); - $form->sessionMessage(_t("MemberForm.DETAILSSAVED", 'Your details have been saved'), 'good'); + $form->sessionMessage(_t("MemberForm.DetailsSaved", 'Your details have been saved'), 'good'); $this->extend('updateShopAccountFormResponse', $request, $form, $data, $response); @@ -96,7 +83,7 @@ public function proceed($data, $form, $request) $form->saveInto($member); $member->write(); - $form->sessionMessage(_t("MemberForm.DETAILSSAVED", 'Your details have been saved'), 'good'); + $form->sessionMessage(_t("MemberForm.DetailsSaved", 'Your details have been saved'), 'good'); $this->extend('updateShopAccountFormResponse', $request, $form, $data, $response); @@ -119,22 +106,24 @@ public function php($data) $valid = parent::php($data); $field = (string)Member::config()->unique_identifier_field; if (isset($data[$field])) { - $uid = $data[(string)Member::config()->unique_identifier_field]; - $currentmember = Member::currentUser(); + $uid = $data[$field]; + $currentMember = Member::currentUser(); + //can't be taken - if (DataObject::get_one('Member', "$field = '$uid' AND ID != " . $currentmember->ID)) { + if (Member::get()->filter($field, $uid)->exclude('ID', $currentMember->ID)->count() > 0) { // get localized field labels - $fieldLabels = $currentmember->fieldLabels(false); + $fieldLabels = $currentMember->fieldLabels(false); // if a localized value exists, use this for our error-message $fieldLabel = isset($fieldLabels[$field]) ? $fieldLabels[$field] : $field; $this->validationError( $field, // re-use the message from checkout - sprintf( - _t("Checkout.MEMBEREXISTS", "A member already exists with the %s %s"), - $fieldLabel, - $uid + _t( + 'Checkout.MemberExists', + 'A member already exists with the {Field} {Identifier}', + '', + array('Field' => $fieldLabel, 'Identifier' => $uid) ), "required" ); diff --git a/code/account/ShopMemberFactory.php b/code/account/ShopMemberFactory.php index 7d444dfd2..a348e55ba 100644 --- a/code/account/ShopMemberFactory.php +++ b/code/account/ShopMemberFactory.php @@ -17,19 +17,24 @@ public function create($data) $result = ValidationResult::create(); if (!Checkout::member_creation_enabled()) { $result->error( - _t("Checkout.MEMBERSHIPSNOTALLOWED", "Creating new memberships is not allowed") + _t("Checkout.MembershipIsNotAllowed", "Creating new memberships is not allowed") ); throw new ValidationException($result); } $idfield = Config::inst()->get('Member', 'unique_identifier_field'); if (!isset($data[$idfield]) || empty($data[$idfield])) { $result->error( - sprintf(_t("Checkout.IDFIELDNOTFOUND", "Required field not found: %s"), $idfield) + _t( + 'Checkout.IdFieldNotFound', + 'Required field not found: {IdentifierField}', + 'Identifier is the field that holds the unique user-identifier, commonly this is \'Email\'', + array('IdentifierField' => $idfield) + ) ); throw new ValidationException($result); } if (!isset($data['Password']) || empty($data['Password'])) { - $result->error(_t("Checkout.PASSWORDREQUIRED", "A password is required")); + $result->error(_t("Checkout.PasswordRequired", "A password is required")); throw new ValidationException($result); } $idval = $data[$idfield]; @@ -40,10 +45,11 @@ public function create($data) $fieldLabel = isset($fieldLabels[$idfield]) ? $fieldLabels[$idfield] : $idfield; $result->error( - sprintf( - _t("Checkout.MEMBEREXISTS", "A member already exists with the %s %s"), - $fieldLabel, - $idval + _t( + 'Checkout.MemberExists', + 'A member already exists with the {Field} {Identifier}', + '', + array('Field' => $fieldLabel, 'Identifier' => $idval) ) ); throw new ValidationException($result); diff --git a/code/address/SetLocationForm.php b/code/address/SetLocationForm.php index 4863cb1c1..49670f31b 100644 --- a/code/address/SetLocationForm.php +++ b/code/address/SetLocationForm.php @@ -6,10 +6,10 @@ public function __construct($controller, $name = "SetLocationForm") { $countries = SiteConfig::current_site_config()->getCountriesList(); $fields = FieldList::create( - $countryfield = DropdownField::create("Country", _t('SetLocationForm.COUNTRY', 'Country'), $countries) + $countryfield = DropdownField::create("Country", _t('SetLocationForm.Country', 'Country'), $countries) ); $countryfield->setHasEmptyDefault(true); - $countryfield->setEmptyString(_t('SetLocationForm.CHOOSECOUNTRY', 'Choose country...')); + $countryfield->setEmptyString(_t('SetLocationForm.ChooseCountry', 'Choose country...')); $actions = FieldList::create( FormAction::create("setLocation", "set") ); diff --git a/code/cart/CartEditField.php b/code/cart/CartEditField.php index 568da8411..560b8e858 100644 --- a/code/cart/CartEditField.php +++ b/code/cart/CartEditField.php @@ -116,13 +116,13 @@ protected function editableItems() if ($variations->exists()) { $variationfield = DropdownField::create( $name . "[ProductVariationID]", - _t('CartEditField.VARIATION', "Variation"), + _t('Variation.SINGULARNAME', "Variation"), $variations->map('ID', 'Title'), $item->ProductVariationID ); } } - $remove = CheckboxField::create($name . "[Remove]", _t('CartEditField.REMOVE', "Remove")); + $remove = CheckboxField::create($name . "[Remove]", _t('Shop.Remove', "Remove")); $editables->push( $item->customise( array( diff --git a/code/cart/CartForm.php b/code/cart/CartForm.php index 33759a912..27f055299 100644 --- a/code/cart/CartForm.php +++ b/code/cart/CartForm.php @@ -17,7 +17,7 @@ public function __construct($controller, $name = "CartForm", $cart = null, $temp ->setTemplate($template) ); $actions = FieldList::create( - FormAction::create("updatecart", _t('CartForm.UPDATE_CART', "Update Cart")) + FormAction::create("updatecart", _t('CartForm.UpdateCart', "Update Cart")) ); parent::__construct($controller, $name, $fields, $actions); diff --git a/code/cart/CartPage.php b/code/cart/CartPage.php index ca2bb9bd1..50162a80d 100644 --- a/code/cart/CartPage.php +++ b/code/cart/CartPage.php @@ -42,16 +42,15 @@ public function getCMSFields() ) ); } - if ($pgroups = ProductCategory::get()) { - $fields->addFieldToTab( - 'Root.Links', - DropdownField::create( - 'ContinuePageID', - _t('CartPage.has_one_ContinuePage', 'Continue Product Group Page'), - $pgroups->map("ID", "Title") - ) - ); - } + + $fields->addFieldToTab( + 'Root.Links', + TreeDropdownField::create( + 'ContinuePageID', + _t('CartPage.has_one_ContinuePage', 'Continue Shopping Page'), + 'SiteTree' + ) + ); return $fields; } @@ -95,7 +94,7 @@ public function Title() if ($this->Title) { return $this->Title; } - return _t('CartPage.TITLE', "Shopping Cart"); + return _t('CartPage.DefaultTitle', "Shopping Cart"); } /** @@ -111,15 +110,6 @@ public function CartForm() $this->extend('updateCartForm', $form); - // if accessing through the model, warn that the hook is going to go away - $this->dataRecord->extend('updateCartForm', $form, $cart); - if ($this->dataRecord->hasMethod('updateCartForm')) { - Deprecation::notice( - '2.0', - 'Please access updateCartForm through CartPage_Controller instead of CartPage (this extension point is due to be removed)' - ); - } - return $form; } } diff --git a/code/cart/OrderTotalCalculator.php b/code/cart/OrderTotalCalculator.php index d87069ac9..c7dafe558 100644 --- a/code/cart/OrderTotalCalculator.php +++ b/code/cart/OrderTotalCalculator.php @@ -27,25 +27,52 @@ function calculate() if (!is_array($modifierclasses) || empty($modifierclasses)) { return $runningtotal; } - foreach ($modifierclasses as $ClassName) { - if ($modifier = $this->getModifier($ClassName)) { - $modifier->Sort = $sort; - $runningtotal = $modifier->modify($runningtotal); - if ($modifier->isChanged()) { - $modifier->write(); + + if (ShopTools::DBConn()->supportsTransactions()) { + ShopTools::DBConn()->transactionStart(); + } + + set_error_handler(function ($severity, $message, $file, $line) { + throw new ErrorException($message, 0, $severity, $file, $line); + }, E_ALL & ~(E_STRICT | E_NOTICE)); + + try { + foreach ($modifierclasses as $ClassName) { + if ($modifier = $this->getModifier($ClassName)) { + $modifier->Sort = $sort; + $runningtotal = $modifier->modify($runningtotal); + if ($modifier->isChanged()) { + $modifier->write(); + } } + $sort++; } - $sort++; - } - //clear old modifiers out - if ($existingmodifiers) { - foreach ($existingmodifiers as $modifier) { - if (!in_array($modifier->ClassName, $modifierclasses)) { - $modifier->delete(); - $modifier->destroy(); + //clear old modifiers out + if ($existingmodifiers) { + foreach ($existingmodifiers as $modifier) { + if (!in_array($modifier->ClassName, $modifierclasses)) { + $modifier->delete(); + $modifier->destroy(); + } } } + } catch (Exception $ex) { + // Rollback the transaction if an error occurred + if (ShopTools::DBConn()->supportsTransactions()) { + ShopTools::DBConn()->transactionRollback(); + } + // throw the exception after rollback + throw $ex; + } finally { + // restore the error handler, no matter what + restore_error_handler(); + } + + // Everything went through fine, complete the transaction + if (ShopTools::DBConn()->supportsTransactions()) { + ShopTools::DBConn()->transactionEnd(); } + //prevent negative sales from ever occurring if ($runningtotal < 0) { SS_Log::log( diff --git a/code/cart/ShopQuantityField.php b/code/cart/ShopQuantityField.php index 32d6b1b61..d54b28a0b 100644 --- a/code/cart/ShopQuantityField.php +++ b/code/cart/ShopQuantityField.php @@ -67,7 +67,7 @@ public function Field() return NumericField::create( $this->MainID() . '_Quantity', // this title currently doesn't show up in the front end, better assign a translation anyway. - _t('AddProductForm.Quantity', "Quantity"), + _t('Order.Quantity', "Quantity"), $this->item->Quantity )->setAttribute('type', 'number'); } @@ -130,7 +130,7 @@ public function Field() return DropdownField::create( $this->MainID() . '_Quantity', // this title currently doesn't show up in the front end, better assign a translation anyway. - _t('AddProductForm.Quantity', "Quantity"), + _t('Order.Quantity', "Quantity"), $qtyArray, ($this->item->Quantity) ? $this->item->Quantity : "" ); diff --git a/code/cart/ShoppingCart.php b/code/cart/ShoppingCart.php index 135b27acd..366fd754b 100644 --- a/code/cart/ShoppingCart.php +++ b/code/cart/ShoppingCart.php @@ -13,13 +13,13 @@ class ShoppingCart extends Object { private static $cartid_session_name = 'shoppingcartid'; - private $order; + private $order; - private $calculateonce = false; + private $calculateonce = false; - private $message; + private $message; - private $type; + private $type; /** * Access for only allowing access to one (singleton) ShoppingCart. @@ -53,7 +53,7 @@ public function current() $this->order = Order::get()->filter( array( "Status" => "Cart", - "ID" => $sessionid, + "ID" => $sessionid, ) )->first(); } @@ -108,7 +108,7 @@ protected function findOrMake() * Adds an item to the cart * * @param Buyable $buyable - * @param number $quantity + * @param number $quantity * @param unknown $filter * * @return boolean|OrderItem false or the new/existing item @@ -119,7 +119,7 @@ public function add(Buyable $buyable, $quantity = 1, $filter = array()) $order->extend("beforeAdd", $buyable, $quantity, $filter); if (!$buyable) { - return $this->error(_t("ShoppingCart.PRODUCTNOTFOUND", "Product not found.")); + return $this->error(_t("ShoppingCart.ProductNotFound", "Product not found.")); } $item = $this->findOrMakeItem($buyable, $filter); if (!$item) { @@ -133,7 +133,7 @@ public function add(Buyable $buyable, $quantity = 1, $filter = array()) } $item->write(); $order->extend("afterAdd", $item, $buyable, $quantity, $filter); - $this->message(_t("ShoppingCart.ITEMADD", "Item has been added successfully.")); + $this->message(_t("ShoppingCart.ItemAdded", "Item has been added successfully.")); return $item; } @@ -152,7 +152,7 @@ public function remove(Buyable $buyable, $quantity = null, $filter = array()) $order = $this->current(); if (!$order) { - return $this->error(_t("ShoppingCart.NOORDER", "No current order.")); + return $this->error(_t("ShoppingCart.NoOrder", "No current order.")); } $order->extend("beforeRemove", $buyable, $quantity, $filter); @@ -172,7 +172,7 @@ public function remove(Buyable $buyable, $quantity = null, $filter = array()) $item->write(); } $order->extend("afterRemove", $item, $buyable, $quantity, $filter); - $this->message(_t("ShoppingCart.ITEMREMOVED", "Item has been successfully removed.")); + $this->message(_t("ShoppingCart.ItemRemoved", "Item has been successfully removed.")); return true; } @@ -202,7 +202,7 @@ public function setQuantity(Buyable $buyable, $quantity = 1, $filter = array()) $item->Quantity = $quantity; $item->write(); $order->extend("afterSetQuantity", $item, $buyable, $quantity, $filter); - $this->message(_t("ShoppingCart.QUANTITYSET", "Quantity has been set.")); + $this->message(_t("ShoppingCart.QuantitySet", "Quantity has been set.")); return $item; } @@ -228,14 +228,15 @@ private function findOrMakeItem(Buyable $buyable, $filter = array()) if (!$item) { $member = Member::currentUser(); + $buyable = $this->getCorrectBuyable($buyable); + if (!$buyable->canPurchase($member)) { return $this->error( - sprintf( - _t( - "ShoppingCart.CANNOTPURCHASE", - "This %s cannot be purchased." - ), - strtolower($buyable->i18n_singular_name()) + _t( + 'ShoppingCart.CannotPurchase', + 'This {Title} cannot be purchased.', + '', + array('Title' => $buyable->i18n_singular_name()) ) ); //TODO: produce a more specific message @@ -257,7 +258,7 @@ private function findOrMakeItem(Buyable $buyable, $filter = array()) * Finds an existing order item. * * @param Buyable $buyable - * @param string $filter + * @param string $filter * * @return the item requested, or false */ @@ -267,6 +268,9 @@ public function get(Buyable $buyable, $customfilter = array()) if (!$buyable || !$order) { return false; } + + $buyable = $this->getCorrectBuyable($buyable); + $filter = array( 'OrderID' => $order->ID, ); @@ -280,12 +284,36 @@ public function get(Buyable $buyable, $customfilter = array()) $query = new MatchObjectFilter($itemclass, array_merge($customfilter, $filter), $required); $item = $itemclass::get()->where($query->getFilter())->first(); if (!$item) { - return $this->error(_t("ShoppingCart.ITEMNOTFOUND", "Item not found.")); + return $this->error(_t("ShoppingCart.ItemNotFound", "Item not found.")); } return $item; } + /** + * Ensure the proper buyable will be returned for a given buyable… + * This is being used to ensure a product with variations cannot be added to the cart… + * a Variation has to be added instead! + * @param Buyable $buyable + * @return Buyable + */ + public function getCorrectBuyable(Buyable $buyable) + { + if ( + $buyable instanceof Product && + $buyable->hasExtension('ProductVariationsExtension') && + $buyable->Variations()->count() > 0 + ) { + foreach ($buyable->Variations() as $variation) { + if ($variation->canPurchase()) { + return $variation; + } + } + } + + return $buyable; + } + /** * Store old cart id in session order history * @param int|null $requestedOrderId optional parameter that denotes the order that was requested @@ -311,18 +339,21 @@ public function archiveorderid($requestedOrderId = null) /** * Empty / abandon the entire cart. * + * @param bool $write whether or not to write the abandoned order * @return bool - true if successful, false if no cart found */ - public function clear() + public function clear($write = true) { Session::clear(self::config()->cartid_session_name); $order = $this->current(); $this->order = null; if (!$order) { - return $this->error(_t("ShoppingCart.NOCARTFOUND", "No cart found.")); + return $this->error(_t("ShoppingCart.NoCartFound", "No cart found.")); + } + if ($write) { + $order->write(); } - $order->write(); - $this->message(_t("ShoppingCart.CLEARED", "Cart was successfully cleared.")); + $this->message(_t("ShoppingCart.Cleared", "Cart was successfully cleared.")); return true; } @@ -499,7 +530,7 @@ protected function buyableFromRequest() ) { return $this->httpError( 400, - _t("ShoppingCart.CSRF", "Invalid security token, possible CSRF attack.") + _t("ShoppingCart.InvalidSecurityToken", "Invalid security token, possible CSRF attack.") ); } $id = (int)$request->param('ID'); @@ -525,7 +556,8 @@ protected function buyableFromRequest() //TODO: store error message return null; } - return $buyable; + + return $this->cart->getCorrectBuyable($buyable); } /** @@ -632,7 +664,7 @@ public function index() } elseif ($response = ErrorPage::response_for(404)) { return $response; } - return $this->httpError(404, _t("ShoppingCart.NOCARTINITIALISED", "no cart initialised")); + return $this->httpError(404, _t("ShoppingCart.NoCartInitialised", "no cart initialised")); } /** diff --git a/code/cart/ViewableCart.php b/code/cart/ViewableCart.php index e060a86ab..f7a412eba 100644 --- a/code/cart/ViewableCart.php +++ b/code/cart/ViewableCart.php @@ -25,6 +25,12 @@ public function Cart() public function getContinueLink() { + if($cartPage = CartPage::get()->first()){ + if($cartPage->ContinuePageID){ + return $cartPage->ContinuePage()->Link(); + } + } + $maincategory = ProductCategory::get() ->sort( array( diff --git a/code/checkout/Checkout.php b/code/checkout/Checkout.php index b5323376b..1e9f2374f 100644 --- a/code/checkout/Checkout.php +++ b/code/checkout/Checkout.php @@ -1,5 +1,7 @@ error(_t("Checkout.NOPAYMENTMETHOD", "Payment method does not exist")); + return $this->error(_t("Checkout.NoPaymentMethod", "Payment method does not exist")); } Session::set("Checkout.PaymentMethod", $paymentmethod); return true; @@ -143,15 +145,6 @@ public function getSelectedPaymentMethod($nice = false) return $method; } - /** - * @deprecated 1.0 use ShopMemberFactory - */ - public function createMembership($data) - { - $factory = new ShopMemberFactory(); - return $factory->create($data); - } - /** * Checks if member (or not) is allowed, in accordance with configuration */ diff --git a/code/checkout/CheckoutComponentValidator.php b/code/checkout/CheckoutComponentValidator.php index be0f99574..b1207ef3f 100644 --- a/code/checkout/CheckoutComponentValidator.php +++ b/code/checkout/CheckoutComponentValidator.php @@ -32,7 +32,7 @@ public function php($data) if (!$valid) { $this->form->sessionMessage( _t( - "CheckoutComponentValidator.INVALIDMESSAGE", + "CheckoutComponentValidator.InvalidDataMessage", "There are problems with the data you entered. See below:" ), "bad" diff --git a/code/checkout/CheckoutFieldFactory.php b/code/checkout/CheckoutFieldFactory.php index 612b6fa9e..193aff8d1 100644 --- a/code/checkout/CheckoutFieldFactory.php +++ b/code/checkout/CheckoutFieldFactory.php @@ -1,5 +1,7 @@ getSubset( FieldList::create( - TextField::create('FirstName', _t('CheckoutField.FIRSTNAME', 'First Name')), - TextField::create('Surname', _t('CheckoutField.SURNAME', 'Surname')), - EmailField::create('Email', _t('CheckoutField.EMAIL', 'Email')) + TextField::create('FirstName', _t('Order.db_FirstName', 'First Name')), + TextField::create('Surname', _t('Order.db_Surname', 'Surname')), + EmailField::create('Email', _t('Order.db_Email', 'Email')) ), $subset ); @@ -44,7 +46,7 @@ public function getAddressFields($type = "shipping", $subset = array()) public function getMembershipFields() { $fields = $this->getContactFields(); - $idfield = Member::get_unique_identifier_field(); + $idfield = Member::config()->unique_identifier_field; if (!$fields->fieldByName($idfield)) { $fields->push(TextField::create($idfield, $idfield)); //TODO: scaffold the correct id field } @@ -56,24 +58,26 @@ public function getPasswordFields() { $loginlink = "Security/login?BackURL=" . CheckoutPage::find_link(true); $fields = FieldList::create( - HeaderField::create(_t('CheckoutField.MEMBERSHIPDETAILS', 'Membership Details'), 3), + HeaderField::create(_t('CheckoutField.MembershipDetails', 'Membership Details'), 3), LiteralField::create( 'MemberInfo', '

' . - _t('CheckoutField.MEMBERINFO', 'If you are already a member please') - . " " . - _t('OrderForm.LogIn', 'log in') . - '.' . + _t( + 'CheckoutField.MemberLoginInfo', + 'If you are already a member please log in', + '', + array('LoginUrl' => $loginlink) + ) . '

' ), LiteralField::create( 'AccountInfo', '

' . _t( - 'CheckoutField.ACCOUNTINFO', + 'CheckoutField.AccountInfo', 'Please choose a password, so you can login and check your order history in the future' ) . '

' ), - $this->getPasswordField() + $pwf = $this->getPasswordField() ); if (!Checkout::user_membership_required()) { $pwf->setCanBeEmpty(true); @@ -86,23 +90,23 @@ public function getPaymentMethodFields() //TODO: only get one field if there is no option return OptionsetField::create( 'PaymentMethod', - _t('CheckoutField.PAYMENT_TYPE', "Payment Type"), - GatewayInfo::get_supported_gateways(), - array_keys(GatewayInfo::get_supported_gateways()) + _t('CheckoutField.PaymentType', "Payment Type"), + GatewayInfo::getSupportedGateways(), + array_keys(GatewayInfo::getSupportedGateways()) ); } public function getPasswordField($confirmed = true) { if ($confirmed) { - return ConfirmedPasswordField::create('Password', _t('CheckoutField.PASSWORD', 'Password')); + return ConfirmedPasswordField::create('Password', _t('CheckoutField.Password', 'Password')); } - return PasswordField::create('Password', _t('CheckoutField.PASSWORD', 'Password')); + return PasswordField::create('Password', _t('CheckoutField.Password', 'Password')); } public function getNotesField() { - return TextareaField::create("Notes", _t("CheckoutField.NOTES", "Message")); + return TextareaField::create("Notes", _t("Order.db_Notes", "Message")); } public function getTermsConditionsField() @@ -114,16 +118,11 @@ public function getTermsConditionsField() $field = CheckboxField::create( 'ReadTermsAndConditions', - sprintf( - _t( - 'CheckoutField.TERMSANDCONDITIONS', - "I agree to the terms and conditions stated on the - - terms and conditions - - page" - ), - $termsPage->Link() + _t( + 'Checkout.TermsAndConditionsLink', + 'I agree to the terms and conditions stated on the {TermsPageTitle} page', + '', + array('TermsPageLink' => $termsPage->Link(), 'TermsPageTitle' => $termsPage->Title) ) ); } diff --git a/code/checkout/CheckoutForm.php b/code/checkout/CheckoutForm.php index 9fa9ed111..f5060c56a 100644 --- a/code/checkout/CheckoutForm.php +++ b/code/checkout/CheckoutForm.php @@ -13,7 +13,7 @@ public function __construct($controller, $name, CheckoutComponentConfig $config) $actions = FieldList::create( FormAction::create( 'checkoutSubmit', - _t('CheckoutForm.PROCEED', 'Proceed to payment') + _t('CheckoutPage.ProceedToPayment', 'Proceed to payment') ) ); $validator = CheckoutComponentValidator::create($this->config); diff --git a/code/checkout/CheckoutPage.php b/code/checkout/CheckoutPage.php index c72041be9..7fab3d0fc 100644 --- a/code/checkout/CheckoutPage.php +++ b/code/checkout/CheckoutPage.php @@ -46,7 +46,7 @@ public function getCMSFields() ) ->setDescription( _t( - 'CheckoutPage.PURCHASE_COMPLETE_DESCRIPTION', + 'CheckoutPage.PurchaseCompleteDescription', "This message is included in reciept email, after the customer submits the checkout" ) ), @@ -105,7 +105,7 @@ public function Title() return $this->Title; } - return _t('CheckoutPage.TITLE', "Checkout"); + return _t('CheckoutPage.DefaultTitle', "Checkout"); } public function OrderForm() @@ -164,7 +164,7 @@ public function PaymentForm() $form->setActions( FieldList::create( - FormAction::create("submitpayment", _t('CheckoutForm.SubmitPayment', "Submit Payment")) + FormAction::create("submitpayment", _t('CheckoutPage.SubmitPayment', "Submit Payment")) ) ); diff --git a/code/checkout/OrderEmail.php b/code/checkout/OrderEmail.php deleted file mode 100644 index b915be535..000000000 --- a/code/checkout/OrderEmail.php +++ /dev/null @@ -1,23 +0,0 @@ -first(); $completemessage = $checkoutpage ? $checkoutpage->PurchaseComplete : ''; - $email = Email::create(); + /** @var Email $email */ + $email = Injector::inst()->create('ShopEmail'); $email->setTemplate($template); $email->setFrom($from); $email->setTo($to); @@ -86,9 +87,11 @@ public function sendEmail($template, $subject, $copyToAdmin = true) */ public function sendConfirmation() { - $subject = sprintf( - _t("OrderNotifier.CONFIRMATIONSUBJECT", "Order #%d Confirmation"), - $this->order->Reference + $subject = _t( + 'ShopEmail.ConfirmationSubject', + 'Order #{OrderNo} confirmation', + '', + array('OrderNo' => $this->order->Reference) ); $this->sendEmail( 'Order_ConfirmationEmail', @@ -102,9 +105,11 @@ public function sendConfirmation() */ public function sendAdminNotification() { - $subject = sprintf( - _t("OrderNotifier.ADMINNOTIFICATIONSUBJECT", "Order #%d Notification"), - $this->order->Reference + $subject = _t( + 'ShopEmail.AdminNotificationSubject', + 'Order #{OrderNo} notification', + '', + array('OrderNo' => $this->order->Reference) ); $this->buildEmail('Order_AdminNotificationEmail', $subject) @@ -118,17 +123,38 @@ public function sendAdminNotification() */ public function sendReceipt() { - $subject = sprintf( - _t("OrderNotifier.RECEIPTSUBJECT", "Order #%d Receipt"), - $this->order->Reference + $subject = _t( + 'ShopEmail.ReceiptSubject', + 'Order #{OrderNo} receipt', + '', + array('OrderNo' => $this->order->Reference) ); + $this->sendEmail( 'Order_ReceiptEmail', $subject, self::config()->bcc_receipt_to_admin ); - $this->order->ReceiptSent = SS_Datetime::now()->Rfc2822(); - $this->order->write(); + } + + /** + * Sends an email to the admin that an order has been cancelled + */ + public function sendCancelNotification() + { + $email = Injector::inst()->create( + 'ShopEmail', + Email::config()->admin_email, + Email::config()->admin_email, + _t( + 'ShopEmail.CancelSubject', + 'Order #{OrderNo} cancelled by member', + '', + array('OrderNo' => $this->order->Reference) + ), + $this->order->renderWith('Order') + ); + $email->send(); } /** @@ -158,7 +184,8 @@ public function sendStatusChange($title, $note = null) } else { $adminEmail = Email::config()->admin_email; } - $e = Order_statusEmail::create(); + $e = Injector::inst()->create('ShopEmail'); + $e->setTemplate('Order_StatusEmail'); $e->populateTemplate( array( "Order" => $this->order, diff --git a/code/checkout/OrderProcessor.php b/code/checkout/OrderProcessor.php index f3cb33cb0..075790f83 100644 --- a/code/checkout/OrderProcessor.php +++ b/code/checkout/OrderProcessor.php @@ -1,5 +1,9 @@ createPayment($gateway); if (!$payment) { //errors have been stored. - return false; + return null; } - // Create a purchase service, and set the user-facing success URL for redirects - $service = PurchaseService::create($payment) - ->setReturnUrl($this->getReturnUrl()); + $payment->setSuccessUrl($successUrl ? $successUrl : $this->getReturnUrl()); - // Process payment, get the result back - $response = $service->purchase($this->getGatewayData($gatewaydata)); - if (GatewayInfo::is_manual($gateway)) { - //don't complete the payment at this stage, if payment is manual - $this->placeOrder(); + // Explicitly set the cancel URL + if ($cancelUrl) { + $payment->setFailureUrl($cancelUrl); } - // For an OFFSITE payment, response will now contain a redirect - // For an ONSITE payment, ShopPayment::onCapture will have been called, which will have called completePayment + // Create a payment service, by using the Service Factory. This will automatically choose an + // AuthorizeService or PurchaseService, depending on Gateway configuration. + // Set the user-facing success URL for redirects + /** @var ServiceFactory $factory */ + $factory = ServiceFactory::create(); + $service = $factory->getService($payment, ServiceFactory::INTENT_PAYMENT); + + // Initiate payment, get the result back + try { + $serviceResponse = $service->initiate($this->getGatewayData($gatewaydata)); + } catch (SilverStripe\Omnipay\Exception\Exception $ex) { + // error out when an exception occurs + $this->error($ex->getMessage()); + return null; + } - return $response; + // Check if the service response itself contains an error + if ($serviceResponse->isError()) { + if ($opResponse = $serviceResponse->getOmnipayResponse()) { + $this->error($opResponse->getMessage()); + } else { + $this->error('An unspecified payment error occurred. Please check the payment messages.'); + } + } + + // For an OFFSITE payment, serviceResponse will now contain a redirect + // For an ONSITE payment, ShopPayment::onCaptured will have been called, which will have called completePayment + + return $serviceResponse; } /** @@ -137,10 +168,10 @@ protected function getGatewayData($customData) */ public function createPayment($gateway) { - if (!GatewayInfo::is_supported($gateway)) { + if (!GatewayInfo::isSupported($gateway)) { $this->error( _t( - "PaymentProcessor.INVALID_GATEWAY", + "PaymentProcessor.InvalidGateway", "`{gateway}` isn't a valid payment gateway.", 'gateway is the name of the payment gateway', array('gateway' => $gateway) @@ -149,11 +180,11 @@ public function createPayment($gateway) return false; } if (!$this->order->canPay(Member::currentUser())) { - $this->error(_t("PaymentProcessor.CANTPAY", "Order can't be paid for.")); + $this->error(_t("PaymentProcessor.CantPay", "Order can't be paid for.")); return false; } $payment = Payment::create() - ->init($gateway, $this->order->TotalOutstanding(), ShopConfig::get_base_currency()); + ->init($gateway, $this->order->TotalOutstanding(true), ShopConfig::get_base_currency()); $this->order->Payments()->add($payment); return $payment; } @@ -166,8 +197,8 @@ public function createPayment($gateway) */ public function completePayment() { - if (!$this->order->Paid) { - // recalculate order to be sure we have the correct total + if (!$this->order->IsPaid()) { + // recalculate order to be sure we have the correct total $this->order->calculate(); $this->order->extend('onPayment'); //a payment has been made @@ -181,23 +212,14 @@ public function completePayment() } if ( - // Standard order - ($this->order->GrandTotal() > 0 && $this->order->TotalOutstanding() <= 0) + // Standard order. Only change to 'Paid' once all payments are captured + ($this->order->GrandTotal() > 0 && $this->order->TotalOutstanding(false) <= 0) // Zero-dollar order (e.g. paid with loyalty points) || ($this->order->GrandTotal() == 0 && Order::config()->allow_zero_order_total) ) { //set order as paid $this->order->Status = 'Paid'; - $this->order->Paid = SS_Datetime::now()->Rfc2822(); $this->order->write(); - foreach ($this->order->Items() as $item) { - $item->onPayment(); - } - //all payment is settled - $this->order->extend('onPaid'); - } - if (!$this->order->ReceiptSent) { - $this->notifier->sendReceipt(); } } } @@ -210,17 +232,17 @@ public function completePayment() public function canPlace(Order $order) { if (!$order) { - $this->error(_t("OrderProcessor.NULL", "Order does not exist.")); + $this->error(_t("OrderProcessor.NoOrder", "Order does not exist.")); return false; } //order status is applicable if (!$order->IsCart()) { - $this->error(_t("OrderProcessor.NOTCART", "Order is not a cart.")); + $this->error(_t("OrderProcessor.NotCart", "Order is not a cart.")); return false; } //order has products if ($order->Items()->Count() <= 0) { - $this->error(_t("OrderProcessor.NOITEMS", "Order has no items.")); + $this->error(_t("OrderProcessor.NoItems", "Order has no items.")); return false; } @@ -237,7 +259,7 @@ public function canPlace(Order $order) public function placeOrder() { if (!$this->order) { - $this->error(_t("OrderProcessor.NULL", "A new order has not yet been started.")); + $this->error(_t("OrderProcessor.NoOrderStarted", "A new order has not yet been started.")); return false; } if (!$this->canPlace($this->order)) { //final cart validation @@ -251,13 +273,12 @@ public function placeOrder() // recalculate order to be sure we have the correct total $this->order->calculate(); - //remove from session - $cart = ShoppingCart::curr(); - if ($cart && $cart->ID == $this->order->ID) { - ShoppingCart::singleton()->clear(); + if (ShopTools::DBConn()->supportsTransactions()) { + ShopTools::DBConn()->transactionStart(); } + //update status - if ($this->order->TotalOutstanding()) { + if ($this->order->TotalOutstanding(false)) { $this->order->Status = 'Unpaid'; } else { $this->order->Status = 'Paid'; @@ -268,33 +289,64 @@ public function placeOrder() $this->order->IPAddress = $request->getIP(); //record client IP } } - //re-write all attributes and modifiers to make sure they are up-to-date before they can't be changed again - $items = $this->order->Items(); - if ($items->exists()) { - foreach ($items as $item) { - $item->onPlacement(); - $item->write(); + + // Add an error handler that throws an exception upon error, so that we can catch errors as exceptions + // in the following block. + set_error_handler(function ($severity, $message, $file, $line) { + throw new ErrorException($message, 0, $severity, $file, $line); + }, E_ALL & ~(E_STRICT | E_NOTICE)); + + try { + //re-write all attributes and modifiers to make sure they are up-to-date before they can't be changed again + $items = $this->order->Items(); + if ($items->exists()) { + foreach ($items as $item) { + $item->onPlacement(); + $item->write(); + } } - } - $modifiers = $this->order->Modifiers(); - if ($modifiers->exists()) { - foreach ($modifiers as $modifier) { - $modifier->write(); + $modifiers = $this->order->Modifiers(); + if ($modifiers->exists()) { + foreach ($modifiers as $modifier) { + $modifier->write(); + } } - } - //add member to order & customers group - if ($member = Member::currentUser()) { - if (!$this->order->MemberID) { - $this->order->MemberID = $member->ID; + //add member to order & customers group + if ($member = Member::currentUser()) { + if (!$this->order->MemberID) { + $this->order->MemberID = $member->ID; + } + $cgroup = ShopConfig::current()->CustomerGroup(); + if ($cgroup->exists()) { + $member->Groups()->add($cgroup); + } } - $cgroup = ShopConfig::current()->CustomerGroup(); - if ($cgroup->exists()) { - $member->Groups()->add($cgroup); + //allow decorators to do stuff when order is saved. + $this->order->extend('onPlaceOrder'); + $this->order->write(); + } catch (Exception $ex) { + // Rollback the transaction if an error occurred + if (ShopTools::DBConn()->supportsTransactions()) { + ShopTools::DBConn()->transactionRollback(); } + $this->error($ex->getMessage()); + return false; + } finally { + // restore the error handler, no matter what + restore_error_handler(); + } + + // Everything went through fine, complete the transaction + if (ShopTools::DBConn()->supportsTransactions()) { + ShopTools::DBConn()->transactionEnd(); + } + + //remove from session + $cart = ShoppingCart::curr(); + if ($cart && $cart->ID == $this->order->ID) { + // clear the cart, but don't write the order in the process (order is finalized and should NOT be overwritten) + ShoppingCart::singleton()->clear(false); } - //allow decorators to do stuff when order is saved. - $this->order->extend('onPlaceOrder'); - $this->order->write(); //send confirmation if configured and receipt hasn't been sent if ( @@ -328,7 +380,7 @@ public function getError() return $this->error; } - private function error($message) + protected function error($message) { $this->error = $message; } @@ -337,40 +389,4 @@ public static function config() { return new Config_ForClass("OrderProcessor"); } - - /** - * @deprecated 2.0 - */ - public function sendEmail($template, $subject, $copyToAdmin = true) - { - Deprecation::notice('2.0', 'Use OrderEmailNotifier instead'); - return $this->notifier->sendEmail($template, $subject, $copyToAdmin); - } - - /** - * @deprecated 2.0 - */ - public function sendConfirmation() - { - Deprecation::notice('2.0', 'Use OrderEmailNotifier instead'); - $this->notifier->sendConfirmation(); - } - - /** - * @deprecated 2.0 - */ - public function sendReceipt() - { - Deprecation::notice('2.0', 'Use OrderEmailNotifier instead'); - $this->notifier->sendReceipt(); - } - - /** - * @deprecated 2.0 - */ - public function sendStatusChange($title, $note = null) - { - Deprecation::notice('2.0', 'Use OrderEmailNotifier instead'); - $this->notifier->sendStatusChange($title, $note); - } } diff --git a/code/checkout/PaymentForm.php b/code/checkout/PaymentForm.php index acea1e8e5..165c1322e 100644 --- a/code/checkout/PaymentForm.php +++ b/code/checkout/PaymentForm.php @@ -1,5 +1,7 @@ config->getOrder(); $gateway = Checkout::get($order)->getSelectedPaymentMethod(false); if ( - GatewayInfo::is_offsite($gateway) - || GatewayInfo::is_manual($gateway) + GatewayInfo::isOffsite($gateway) + || GatewayInfo::isManual($gateway) || $this->config->getComponentByType('OnsitePaymentCheckoutComponent') ) { return $this->submitpayment($data, $form); @@ -73,10 +75,9 @@ public function checkoutSubmit($data, $form) public function submitpayment($data, $form) { $data = $form->getData(); - if ($this->getSuccessLink()) { - $data['returnUrl'] = $this->getSuccessLink(); - } - $data['cancelUrl'] = $this->getFailureLink() ? $this->getFailureLink() : $this->controller->Link(); + + $cancelUrl = $this->getFailureLink() ? $this->getFailureLink() : $this->controller->Link(); + $order = $this->config->getOrder(); // final recalculation, before making payment @@ -98,7 +99,7 @@ public function submitpayment($data, $form) $form->sessionMessage($this->orderProcessor->getError()); return $this->controller->redirectBack(); } - $data['cancelUrl'] = $this->orderProcessor->getReturnUrl(); + $cancelUrl = $this->orderProcessor->getReturnUrl(); } // if we got here from checkoutSubmit and there's a namespaced OnsitePaymentCheckoutComponent @@ -115,21 +116,16 @@ public function submitpayment($data, $form) // This is where the payment is actually attempted $paymentResponse = $this->orderProcessor->makePayment( Checkout::get($order)->getSelectedPaymentMethod(false), - $data + $data, + $this->getSuccessLink(), + $cancelUrl ); $response = null; - if ($paymentResponse) { - if ($this->controller->hasMethod('processPaymentResponse')) { - $response = $this->controller->processPaymentResponse($paymentResponse, $form); - } else { - if ($paymentResponse->isRedirect() || $paymentResponse->isSuccessful()) { - $response = $paymentResponse->redirect(); - } else { - $form->sessionMessage($paymentResponse->getMessage(), 'bad'); - $response = $this->controller->redirectBack(); - } - } + if ($this->controller->hasMethod('processPaymentResponse')) { + $response = $this->controller->processPaymentResponse($paymentResponse, $form); + } else if ($paymentResponse && !$paymentResponse->isError()) { + $response = $paymentResponse->redirectOrRespond(); } else { $form->sessionMessage($this->orderProcessor->getError(), 'bad'); $response = $this->controller->redirectBack(); diff --git a/code/checkout/components/AddressBookCheckoutComponent.php b/code/checkout/components/AddressBookCheckoutComponent.php index 9600eb60f..e09f9c68e 100644 --- a/code/checkout/components/AddressBookCheckoutComponent.php +++ b/code/checkout/components/AddressBookCheckoutComponent.php @@ -4,7 +4,7 @@ * Adds the ability to use the member's address book for choosing addresses * */ -abstract class AddressBookCheckoutComponent extends AddressCheckoutComponent +abstract class AddressBookCheckoutComponent extends AddressCheckoutComponent implements i18nEntityProvider { private static $composite_field_tag = 'div'; @@ -22,7 +22,7 @@ public function getFormFields(Order $order) // group under a composite field (invisible by default) so we // easily know which fields to show/hide $label = _t( - "AddressBookCheckkoutComponent.{$this->addresstype}Address", + "Address.{$this->addresstype}Address", "{$this->addresstype} Address" ); @@ -47,20 +47,10 @@ public function getExistingAddressFields() $member = Member::currentUser(); if ($member && $member->AddressBook()->exists()) { $addressoptions = $member->AddressBook()->sort('Created', 'DESC')->map('ID', 'toString')->toArray(); - $addressoptions['newaddress'] = _t("AddressBookCheckoutComponent.CREATENEWADDRESS", "Create new address"); + $addressoptions['newaddress'] = _t("Address.CreateNewAddress", "Create new address"); $fieldtype = count($addressoptions) > 3 ? 'DropdownField' : 'OptionsetField'; - $label = ''; - switch ($this->addresstype) { - case 'Billing': - $label = _t("AddressBookCheckoutComponent.EXISTING_BILLING_ADDRESS", "Existing Billing Address"); - break; - case 'Shipping': - $label = _t("AddressBookCheckoutComponent.EXISTING_SHIPPING_ADDRESS", "Existing Shipping Address"); - break; - default: - $label = _t("AddressBookCheckoutComponent.EXISTING_ADDRESS", "Existing Address"); - } + $label = _t("Address.Existing{$this->addresstype}Address", "Existing {$this->addresstype} Address"); return FieldList::create( $fieldtype::create( @@ -146,6 +136,29 @@ public function setData(Order $order, array $data) parent::setData($order, $data); } } + + /** + * Provide translatable entities for this class + * + * @return array + */ + public function provideI18nEntities() + { + if ($this->addresstype) { + return array( + "Address.{$this->addresstype}Address" => array( + "{$this->addresstype} Address", + "Label for the {$this->addresstype} address", + ), + "Address.Existing{$this->addresstype}Address" => array( + "Existing {$this->addresstype} Address", + "Label to select an existing {$this->addresstype} Address", + ), + ); + } + + return array(); + } } class ShippingAddressBookCheckoutComponent extends AddressBookCheckoutComponent diff --git a/code/checkout/components/CheckoutComponentConfig.php b/code/checkout/components/CheckoutComponentConfig.php index 2d6a39473..ae8a17dbe 100644 --- a/code/checkout/components/CheckoutComponentConfig.php +++ b/code/checkout/components/CheckoutComponentConfig.php @@ -1,5 +1,7 @@ addComponent(MembershipCheckoutComponent::create()); } - if (count(GatewayInfo::get_supported_gateways()) > 1) { + if (count(GatewayInfo::getSupportedGateways()) > 1) { $this->addComponent(PaymentCheckoutComponent::create()); } $this->addComponent(NotesCheckoutComponent::create()); diff --git a/code/checkout/components/CustomerDetailsCheckoutComponent.php b/code/checkout/components/CustomerDetailsCheckoutComponent.php index fe3f9745e..5ea76fa9b 100644 --- a/code/checkout/components/CustomerDetailsCheckoutComponent.php +++ b/code/checkout/components/CustomerDetailsCheckoutComponent.php @@ -11,9 +11,9 @@ class CustomerDetailsCheckoutComponent extends CheckoutComponent public function getFormFields(Order $order) { $fields = FieldList::create( - $firstname = TextField::create('FirstName', _t('CheckoutField.FIRSTNAME', 'First Name')), - $surname = TextField::create('Surname', _t('CheckoutField.SURNAME', 'Surname')), - $email = EmailField::create('Email', _t('CheckoutField.EMAIL', 'Email')) + $firstname = TextField::create('FirstName', _t('Order.db_FirstName', 'First Name')), + $surname = TextField::create('Surname', _t('Order.db_Surname', 'Surname')), + $email = EmailField::create('Email', _t('Order.db_Email', 'Email')) ); return $fields; diff --git a/code/checkout/components/MembershipCheckoutComponent.php b/code/checkout/components/MembershipCheckoutComponent.php index 78f1d570c..f51a102fd 100644 --- a/code/checkout/components/MembershipCheckoutComponent.php +++ b/code/checkout/components/MembershipCheckoutComponent.php @@ -54,7 +54,7 @@ public function getRequiredFields(Order $order) return array(); } return array( - Member::get_unique_identifier_field(), + Member::config()->unique_identifier_field, 'Password', ); } @@ -63,10 +63,10 @@ public function getPasswordField() { if ($this->confirmed) { //relies on fix: https://github.com/silverstripe/silverstripe-framework/pull/2757 - return ConfirmedPasswordField::create('Password', _t('CheckoutField.PASSWORD', 'Password')) + return ConfirmedPasswordField::create('Password', _t('CheckoutField.Password', 'Password')) ->setCanBeEmpty(!Checkout::membership_required()); } - return PasswordField::create('Password', _t('CheckoutField.PASSWORD', 'Password')); + return PasswordField::create('Password', _t('CheckoutField.Password', 'Password')); } public function validateData(Order $order, array $data) @@ -85,12 +85,13 @@ public function validateData(Order $order, array $data) // if a localized value exists, use this for our error-message $fieldLabel = isset($fieldLabels[$idfield]) ? $fieldLabels[$idfield] : $idfield; $result->error( - sprintf( - _t("Checkout.MEMBEREXISTS", "A member already exists with the %s %s"), - $fieldLabel, - $idval + _t( + 'Checkout.MemberExists', + 'A member already exists with the {Field} {Identifier}', + '', + array('Field' => $fieldLabel, 'Identifier' => $idval) ), - $idval + $idfield ); } $passwordresult = $this->passwordvalidator->validate($data['Password'], $member); diff --git a/code/checkout/components/NotesCheckoutComponent.php b/code/checkout/components/NotesCheckoutComponent.php index 9801ceca2..68a68b9e5 100644 --- a/code/checkout/components/NotesCheckoutComponent.php +++ b/code/checkout/components/NotesCheckoutComponent.php @@ -5,7 +5,7 @@ class NotesCheckoutComponent extends CheckoutComponent public function getFormFields(Order $order) { return FieldList::create( - TextareaField::create("Notes", _t("CheckoutField.NOTES", "Message")) + TextareaField::create("Notes", _t("Order.db_Notes", "Message")) ); } diff --git a/code/checkout/components/OnsitePaymentCheckoutComponent.php b/code/checkout/components/OnsitePaymentCheckoutComponent.php index 8aff98d0c..8bbdbf128 100644 --- a/code/checkout/components/OnsitePaymentCheckoutComponent.php +++ b/code/checkout/components/OnsitePaymentCheckoutComponent.php @@ -1,6 +1,8 @@ getSelectedPaymentMethod()); + return GatewayInfo::requiredFields(Checkout::get($order)->getSelectedPaymentMethod()); } public function validateData(Order $order, array $data) @@ -35,7 +37,7 @@ public function validateData(Order $order, array $data) $result = ValidationResult::create(); //TODO: validate credit card data if (!Helper::validateLuhn($data['number'])) { - $result->error(_t('OnsitePaymentCheckoutComponent.CREDIT_CARD_INVALID', 'Credit card is invalid')); + $result->error(_t('OnsitePaymentCheckoutComponent.InvalidCreditCard', 'Credit card is invalid')); throw new ValidationException($result); } } diff --git a/code/checkout/components/PaymentCheckoutComponent.php b/code/checkout/components/PaymentCheckoutComponent.php index 2456ee7de..fc7fb0539 100644 --- a/code/checkout/components/PaymentCheckoutComponent.php +++ b/code/checkout/components/PaymentCheckoutComponent.php @@ -1,16 +1,18 @@ 1) { $fields->push( OptionsetField::create( 'PaymentMethod', - _t("CheckoutFields.PAYMENTTYPE", "Payment Type"), + _t("CheckoutField.PaymentType", "Payment Type"), $gateways, array_keys($gateways) ) @@ -27,7 +29,7 @@ public function getFormFields(Order $order) public function getRequiredFields(Order $order) { - if (count(GatewayInfo::get_supported_gateways()) > 1) { + if (count(GatewayInfo::getSupportedGateways()) > 1) { return array(); } @@ -39,17 +41,14 @@ public function validateData(Order $order, array $data) $result = ValidationResult::create(); if (!isset($data['PaymentMethod'])) { $result->error( - _t('PaymentCheckoutComponent.NO_PAYMENT_METHOD', "Payment method not provided"), + _t('PaymentCheckoutComponent.NoPaymentMethod', "Payment method not provided"), "PaymentMethod" ); throw new ValidationException($result); } - $methods = GatewayInfo::get_supported_gateways(); + $methods = GatewayInfo::getSupportedGateways(); if (!isset($methods[$data['PaymentMethod']])) { - $result->error( - _t('PaymentCheckoutComponent.UNSUPPORTED_GATEWAY', "Gateway not supported"), - "PaymentMethod" - ); + $result->error(_t('PaymentCheckoutComponent.UnsupportedGateway', "Gateway not supported"), "PaymentMethod"); throw new ValidationException($result); } } diff --git a/code/checkout/components/TermsCheckoutComponent.php b/code/checkout/components/TermsCheckoutComponent.php index 160184796..cdeb4f6c8 100644 --- a/code/checkout/components/TermsCheckoutComponent.php +++ b/code/checkout/components/TermsCheckoutComponent.php @@ -11,19 +11,14 @@ public function getFormFields(Order $order) $fields->push( CheckboxField::create( 'ReadTermsAndConditions', - sprintf( - _t( - 'CheckoutField.TERMSANDCONDITIONS', - "I agree to the terms and conditions stated on the - - terms and conditions - - page" - ), - $page->Link() + _t( + 'Checkout.TermsAndConditionsLink', + 'I agree to the terms and conditions stated on the {TermsPageTitle} page', + '', + array('TermsPageLink' => $page->Link(), 'TermsPageTitle' => $page->Title) ) )->setCustomValidationMessage( - _t("CheckoutField.MUSTAGREETOTERMS", "You must agree to the terms and conditions") + _t("CheckoutField.MustAgreeToTerms", "You must agree to the terms and conditions") ) ); } diff --git a/code/checkout/steps/CheckoutStep_Address.php b/code/checkout/steps/CheckoutStep_Address.php index f0b3b1b89..bca442140 100644 --- a/code/checkout/steps/CheckoutStep_Address.php +++ b/code/checkout/steps/CheckoutStep_Address.php @@ -25,7 +25,7 @@ public function shippingaddress() $form->Fields()->push( CheckboxField::create( "SeperateBilling", - _t('CheckoutStep_Address.SEPARATE_BILLING', "Bill to a different address from this") + _t('CheckoutStep_Address.SeperateBilling', "Bill to a different address from this") ) ); $order = $this->shippingconfig()->getOrder(); @@ -41,7 +41,7 @@ public function ShippingAddressForm() $form = CheckoutForm::create($this->owner, 'ShippingAddressForm', $this->shippingconfig()); $form->setActions( FieldList::create( - FormAction::create("setshippingaddress", _t('CheckoutStep.CONTINUE', "Continue")) + FormAction::create("setshippingaddress", _t('CheckoutStep.Continue', "Continue")) ) ); $this->owner->extend('updateShippingAddressForm', $form); @@ -82,7 +82,7 @@ public function BillingAddressForm() $form = CheckoutForm::create($this->owner, 'BillingAddressForm', $this->billingconfig()); $form->setActions( FieldList::create( - FormAction::create("setbillingaddress", _t('CheckoutStep.CONTINUE', "Continue")) + FormAction::create("setbillingaddress", _t('CheckoutStep.Continue', "Continue")) ) ); $this->owner->extend('updateBillingAddressForm', $form); diff --git a/code/checkout/steps/CheckoutStep_ContactDetails.php b/code/checkout/steps/CheckoutStep_ContactDetails.php index 82acaf6f0..c8bfa4d76 100644 --- a/code/checkout/steps/CheckoutStep_ContactDetails.php +++ b/code/checkout/steps/CheckoutStep_ContactDetails.php @@ -14,9 +14,12 @@ public function contactdetails() ShoppingCart::curr() && Config::inst()->get("CheckoutStep_ContactDetails", "skip_if_logged_in") ) { - if (Member::currentUser() && !$form->getValidator()->validate()) { - Controller::curr()->redirect($this->NextStepLink()); - return; + if (Member::currentUser()) { + if(!$form->getValidator()->validate()) { + return Controller::curr()->redirect($this->NextStepLink()); + } else { + $form->clearMessage(); + } } } @@ -37,7 +40,7 @@ public function ContactDetailsForm() $form->setRedirectLink($this->NextStepLink()); $form->setActions( FieldList::create( - FormAction::create("checkoutSubmit", _t('CheckoutStep.CONTINUE', "Continue")) + FormAction::create("checkoutSubmit", _t('CheckoutStep.Continue', "Continue")) ) ); $this->owner->extend('updateContactDetailsForm', $form); diff --git a/code/checkout/steps/CheckoutStep_Membership.php b/code/checkout/steps/CheckoutStep_Membership.php index 4e51c0b12..8000704d6 100644 --- a/code/checkout/steps/CheckoutStep_Membership.php +++ b/code/checkout/steps/CheckoutStep_Membership.php @@ -45,12 +45,12 @@ public function MembershipForm() FormAction::create( "createaccount", _t( - 'CheckoutStep_Membership.CREATE_ACCOUNT', + 'CheckoutStep_Membership.CreateAccount', "Create an Account", 'This is an option presented to the user' ) ), - FormAction::create("guestcontinue", _t('CheckoutStep_Membership.CONTINUE_AS_GUEST', "Continue as Guest")) + FormAction::create("guestcontinue", _t('CheckoutStep_Membership.ContinueAsGuest', "Continue as Guest")) ); $form = Form::create($this->owner, 'MembershipForm', $fields, $actions); $this->owner->extend('updateMembershipForm', $form); @@ -108,7 +108,7 @@ public function CreateAccountForm() FormAction::create( 'docreateaccount', _t( - 'CheckoutStep_Membership.CREATE_NEW_ACCOUNT', + 'CheckoutStep_Membership.CreateNewAccount', 'Create New Account', 'This is an action (Button label)' ) diff --git a/code/checkout/steps/CheckoutStep_PaymentMethod.php b/code/checkout/steps/CheckoutStep_PaymentMethod.php index 739b4dfa6..0945001bf 100644 --- a/code/checkout/steps/CheckoutStep_PaymentMethod.php +++ b/code/checkout/steps/CheckoutStep_PaymentMethod.php @@ -1,5 +1,7 @@ owner->redirect($this->NextStepLink()); } @@ -31,7 +33,7 @@ public function PaymentMethodForm() $form = CheckoutForm::create($this->owner, "PaymentMethodForm", $this->checkoutconfig()); $form->setActions( FieldList::create( - FormAction::create("setpaymentmethod", _t('CheckoutStep.CONTINUE', "Continue")) + FormAction::create("setpaymentmethod", _t('CheckoutStep.Continue', "Continue")) ) ); $this->owner->extend('updatePaymentMethodForm', $form); diff --git a/code/cms/OrdersAdmin.php b/code/cms/OrdersAdmin.php index e17357bc7..8286368d0 100644 --- a/code/cms/OrdersAdmin.php +++ b/code/cms/OrdersAdmin.php @@ -97,7 +97,7 @@ public function ItemEditForm() LiteralField::create( "PrintOrder", "" + . _t("Order.Print", "Print") . "" ) ); @@ -114,7 +114,7 @@ public function printorder() if (isset($_REQUEST['print']) && $_REQUEST['print']) { Requirements::customScript("if(document.location.href.indexOf('print=1') > 0) {window.print();}"); } - $title = i18n::_t("ORDER.INVOICE", "Invoice"); + $title = i18n::_t("Order.Invoice", "Invoice"); if ($id = $this->popupController->getRequest()->param('ID')) { $title .= " #$id"; } diff --git a/code/cms/ShopConfig.php b/code/cms/ShopConfig.php index d7cb3e0a0..54d9ffcb7 100644 --- a/code/cms/ShopConfig.php +++ b/code/cms/ShopConfig.php @@ -34,28 +34,28 @@ public function updateCMSFields(FieldList $fields) "Main", TreeDropdownField::create( 'TermsPageID', - _t("ShopConfig.TERMSPAGE", 'Terms and Conditions Page'), + _t("ShopConfig.TermsPage", 'Terms and Conditions Page'), 'SiteTree' ), TreeDropdownField::create( "CustomerGroupID", - _t("ShopConfig.CUSTOMERGROUP", "Group to add new customers to"), + _t("ShopConfig.CustomerGroup", "Group to add new customers to"), "Group" ), - UploadField::create('DefaultProductImage', _t('ShopConfig.DEFAULTIMAGE', 'Default Product Image')) + UploadField::create('DefaultProductImage', _t('ShopConfig.DefaultImage', 'Default Product Image')) ), $countriestab = Tab::create( "Countries", CheckboxSetField::create( 'AllowedCountries', - _t('ShopConfig.ALLOWED_COUNTRIES', 'Allowed Ordering and Shipping Countries'), + _t('ShopConfig.AllowedCountries', 'Allowed Ordering and Shipping Countries'), self::config()->iso_3166_country_codes ) ) ) ); $fields->removeByName("CreateTopLevelGroups"); - $countriestab->setTitle(_t('ShopConfig.ALLOWED_COUNTRIES_TAB_TITLE', "Allowed Countries")); + $countriestab->setTitle(_t('ShopConfig.AllowedCountriesTabTitle', "Allowed Countries")); } public static function get_base_currency() diff --git a/code/cms/dashboard/DashboardRecentMembersPanel.php b/code/cms/dashboard/DashboardRecentMembersPanel.php index 7013ff6dc..8a2bf1c74 100644 --- a/code/cms/dashboard/DashboardRecentMembersPanel.php +++ b/code/cms/dashboard/DashboardRecentMembersPanel.php @@ -29,7 +29,7 @@ public function getConfiguration() { $fields = parent::getConfiguration(); $fields->push( - TextField::create("Count", _t('ShopDashboard.NUMBER_OF_ACCOUNTS', "Number of accounts to show")) + TextField::create("Count", _t('ShopDashboard.NumberOfAccounts', "Number of accounts to show")) ); return $fields; } diff --git a/code/cms/dashboard/DashboardRecentOrdersChart.php b/code/cms/dashboard/DashboardRecentOrdersChart.php index ffe48bf13..bc6ca16bd 100644 --- a/code/cms/dashboard/DashboardRecentOrdersChart.php +++ b/code/cms/dashboard/DashboardRecentOrdersChart.php @@ -26,7 +26,7 @@ public function getDescription() public function getConfiguration() { $fields = parent::getConfiguration(); - $fields->push(TextField::create("Days", _t('ShopDashboard.NUMBER_OF_DAYS', "Number of days to show"))); + $fields->push(TextField::create("Days", _t('ShopDashboard.NumberOfDays', "Number of days to show"))); return $fields; } diff --git a/code/cms/dashboard/DashboardRecentOrdersPanel.php b/code/cms/dashboard/DashboardRecentOrdersPanel.php index ae747fa94..8c6d4d1b7 100644 --- a/code/cms/dashboard/DashboardRecentOrdersPanel.php +++ b/code/cms/dashboard/DashboardRecentOrdersPanel.php @@ -17,18 +17,18 @@ class DashboardRecentOrdersPanel extends DashboardPanel public function getLabel() { - return _t('ShopDashboard.RECENTORDERS', 'Recent Orders'); + return _t('ShopDashboard.RecentOrders', 'Recent Orders'); } public function getDescription() { - return _t('ShopDashboard.RECENTORDERSDESCRIPTION', 'Shows recent orders'); + return _t('ShopDashboard.RecentOrdersDescription', 'Shows recent orders'); } public function getConfiguration() { $fields = parent::getConfiguration(); - $fields->push(TextField::create("Count", _t('ShopDashboard.NUMBER_OF_ORDERS', "Number of orders to show"))); + $fields->push(TextField::create("Count", _t('ShopDashboard.NumberOfOrders', "Number of orders to show"))); return $fields; } diff --git a/code/dev/ShopDevelopmentAdmin.php b/code/dev/ShopDevelopmentAdmin.php index 7945d43e6..90edf8500 100644 --- a/code/dev/ShopDevelopmentAdmin.php +++ b/code/dev/ShopDevelopmentAdmin.php @@ -42,7 +42,7 @@ public function init() //render the debug view $renderer = Object::create('DebugView'); $renderer->writeHeader(); - $renderer->writeInfo(_t("Shop.DEVTOOLSTITLE", "Shop Development Tools"), Director::absoluteBaseURL()); + $renderer->writeInfo(_t("Shop.DevToolsTitle", "Shop Development Tools"), Director::absoluteBaseURL()); } public function ShopFolder() diff --git a/code/exceptions/ShopBuyableException.php b/code/exceptions/ShopBuyableException.php deleted file mode 100644 index 3117765a2..000000000 --- a/code/exceptions/ShopBuyableException.php +++ /dev/null @@ -1,15 +0,0 @@ -getCountryField(), - $addressfield = TextField::create('Address', _t('Address.ADDRESS', 'Address')), - $address2field = TextField::create('AddressLine2', _t('Address.ADDRESSLINE2', 'Address Line 2 (optional)')), - $cityfield = TextField::create('City', _t('Address.CITY', 'City')), - $statefield = TextField::create('State', _t('Address.STATE', 'State')), - $postcodefield = TextField::create('PostalCode', _t('Address.POSTALCODE', 'Postal Code')), - $phonefield = TextField::create('Phone', _t('Address.PHONE', 'Phone Number')) + $addressfield = TextField::create('Address', _t('Address.db_Address', 'Address')), + $address2field = + TextField::create('AddressLine2', _t('Address.db_AddressLine2', 'Address Line 2 (optional)')), + $cityfield = TextField::create('City', _t('Address.db_City', 'City')), + $statefield = TextField::create('State', _t('Address.db_State', 'State')), + $postcodefield = TextField::create('PostalCode', _t('Address.db_PostalCode', 'Postal Code')), + $phonefield = TextField::create('Phone', _t('Address.db_Phone', 'Phone Number')) ); if (isset($params['addfielddescriptions']) && !empty($params['addfielddescriptions'])) { $addressfield->setDescription( - _t("Address.ADDRESSHINT", "street / thoroughfare number, name, and type or P.O. Box") + _t("Address.AddressHint", "street / thoroughfare number, name, and type or P.O. Box") ); - $address2field->setDescription(_t("Address.ADDRESS2HINT", "premises, building, apartment, unit, floor")); - $cityfield->setDescription(_t("Address.CITYHINT", "or suburb, county, district")); - $statefield->setDescription(_t("Address.STATEHINT", "or province, territory, island")); + $address2field->setDescription( + _t("Address.AddressLine2Hint", "premises, building, apartment, unit, floor") + ); + $cityfield->setDescription(_t("Address.CityHint", "or suburb, county, district")); + $statefield->setDescription(_t("Address.StateHint", "or province, territory, island")); } $this->extend('updateFormFields', $fields); @@ -112,13 +115,13 @@ public function getCountryField() //field name is Country_readonly so it's value doesn't get updated return ReadonlyField::create( "Country_readonly", - _t('Address.COUNTRY', 'Country'), + _t('Address.db_Country', 'Country'), array_pop($countries) ); } $field = DropdownField::create( "Country", - _t('Address.COUNTRY', 'Country'), + _t('Address.db_Country', 'Country'), $countries )->setHasEmptyDefault(true); @@ -175,12 +178,14 @@ public function getName() public function toString($separator = ", ") { $fields = array( + $this->Company, + $this->getName(), $this->Address, $this->AddressLine2, $this->City, $this->State, $this->PostalCode, - $this->Country, + $this->Country ); $this->extend('updateToString', $fields); return implode($separator, array_filter($fields)); diff --git a/code/model/Buyable.php b/code/model/Buyable.php index 8167f3043..2ae2d700c 100644 --- a/code/model/Buyable.php +++ b/code/model/Buyable.php @@ -23,12 +23,11 @@ public function createItem($quantity = 1, $filter = array()); /** * Checks if the buyable can be purchased. If a buyable cannot be purchased - * then the method should return a {@link ShopBuyableException} containing - * the messaging. + * then the method should return false * - * @throws ShopBuyableException - * - * @return boolean + * @param Member|null $member the Member that wants to purchase the buyable. Defaults to null + * @param int $quantity the quantity to purchase. Defaults to 1 + * @return boolean true if the buyable can be purchased */ public function canPurchase($member = null, $quantity = 1); diff --git a/code/model/Order.php b/code/model/Order.php index 49d28a0f0..cd1afe339 100644 --- a/code/model/Order.php +++ b/code/model/Order.php @@ -4,6 +4,31 @@ * The order class is a databound object for handling Orders * within SilverStripe. * + * @property string|float Currency + * @property string Reference + * @property string Placed + * @property string Paid + * @property string ReceiptSent + * @property string Printed + * @property string Dispatched + * @property string Status + * @property string FirstName + * @property string Surname + * @property string Email + * @property string Notes + * @property string IPAddress + * @property string|bool SeparateBillingAddress + * @property string Locale + * @property string|int MemberID + * @property string|int ShippingAddressID + * @property string|int BillingAddressID + * @method Member|ShopMember Member + * @method Address BillingAddress + * @method Address ShippingAddress + * @method OrderItem[]|HasManyList Items + * @method OrderModifier[]|HasManyList Modifiers + * @method OrderStatusLog[]|HasManyList OrderStatusLogs + * * @package shop */ class Order extends DataObject @@ -165,7 +190,11 @@ class Order extends DataObject public static function get_order_status_options() { - return singleton('Order')->dbObject('Status')->enumValues(false); + $values = array(); + foreach (singleton('Order')->dbObject('Status')->enumValues(false) as $value) { + $values[$value] = _t('Order.STATUS_' . strtoupper($value), $value); + } + return $values; } /** @@ -215,23 +244,42 @@ public function getDefaultSearchContext() ) ->setMultiple(true) ); - //add date range filtering + + // add date range filtering $fields->insertBefore( - DateField::create("DateFrom", _t('Order.DATE_FROM', "Date from")) + DateField::create("DateFrom", _t('Order.DateFrom', "Date from")) ->setConfig('showcalendar', true), 'Status' ); $fields->insertBefore( - DateField::create("DateTo", _t('Order.DATE_TO', "Date to")) + DateField::create("DateTo", _t('Order.DateTo', "Date to")) ->setConfig('showcalendar', true), 'Status' ); - //get the array, to maniplulate name, and fullname seperately + + // get the array, to maniplulate name, and fullname seperately $filters = $context->getFilters(); $filters['DateFrom'] = GreaterThanFilter::create('Placed'); $filters['DateTo'] = LessThanFilter::create('Placed'); + + // filter customer need to use a bunch of different sources + $filters['FirstName'] = new MultiFieldPartialMatchFilter( + 'FirstName', false, + array('SplitWords'), + array( + 'Surname', + 'Member.FirstName', + 'Member.Surname', + 'BillingAddress.FirstName', + 'BillingAddress.Surname', + 'ShippingAddress.FirstName', + 'ShippingAddress.Surname', + ) + ); + $context->setFilters($filters); + $this->extend('updateDefaultSearchContext', $context); return $context; } @@ -317,15 +365,33 @@ public function GrandTotal() /** * Calculate how much is left to be paid on the order. * Enforces rounding precision. + * + * Payments that have been authorized via a non-manual gateway should count towards the total paid amount. + * However, it's possible to exclude these by setting the $includeAuthorized parameter to false, which is + * useful to determine the status of the Order. Order status should only change to 'Paid' when all + * payments are 'Captured'. + * + * @param bool $includeAuthorized whether or not to include authorized payments (excluding manual payments) + * @return float */ - public function TotalOutstanding() + public function TotalOutstanding($includeAuthorized = true) { return round( - $this->GrandTotal() - $this->TotalPaid(), + $this->GrandTotal() - ($includeAuthorized ? $this->TotalPaidOrAuthorized() : $this->TotalPaid()), self::config()->rounding_precision ); } + /** + * Get the order status. This will return a localized value if available. + * + * @return string the payment status + */ + public function getStatusI18N() + { + return _t('Order.STATUS_' . strtoupper($this->Status), $this->Status); + } + /** * Get the link for finishing order processing. */ @@ -369,7 +435,7 @@ public function canPay($member = null) if (!in_array($this->Status, self::config()->payable_status)) { return false; } - if ($this->TotalOutstanding() > 0 && empty($this->Paid)) { + if ($this->TotalOutstanding(true) > 0 && empty($this->Paid)) { return true; } return false; @@ -426,7 +492,7 @@ public function Currency() } /** - * Get the latest email for this order. + * Get the latest email for this order.z */ public function getLatestEmail() { @@ -486,8 +552,15 @@ protected function getAddress($type) } if (empty($address->Surname) && empty($address->FirstName)) { - $address->FirstName = $this->FirstName; - $address->Surname = $this->Surname; + if ($member = $this->Member()) { + // If there's a member object, use information from the Member. + // The information from Order should have precendence if set though! + $address->FirstName = $this->FirstName ?: $member->FirstName; + $address->Surname = $this->Surname ?: $member->Surname; + } else { + $address->FirstName = $this->FirstName; + $address->Surname = $this->Surname; + } } return $address; @@ -570,13 +643,21 @@ public function getReference() /** * Force creating an order reference */ - public function onBeforeWrite() + protected function onBeforeWrite() { parent::onBeforeWrite(); if (!$this->getField("Reference") && in_array($this->Status, self::$placed_status)) { $this->generateReference(); } + // perform status transition + if ($this->isInDB() && $this->isChanged('Status')) { + $this->statusTransition( + empty($this->original['Status']) ? 'Cart' : $this->original['Status'], + $this->Status + ); + } + // While the order is unfinished/cart, always store the current locale with the order. // We do this everytime an order is saved, because the user might change locale (language-switch). if ($this->Status == 'Cart') { @@ -584,15 +665,52 @@ public function onBeforeWrite() } } + /** + * Called from @see onBeforeWrite whenever status changes + * @param string $fromStatus status to transition away from + * @param string $toStatus target status + */ + protected function statusTransition($fromStatus, $toStatus) + { + // Add extension hook to react to order status transitions. + $this->extend('onStatusChange', $fromStatus, $toStatus); + + if ($toStatus == 'Paid' && !$this->Paid) { + $this->Paid = SS_Datetime::now()->Rfc2822(); + foreach ($this->Items() as $item) { + $item->onPayment(); + } + //all payment is settled + $this->extend('onPaid'); + + if (!$this->ReceiptSent) { + OrderEmailNotifier::create($this)->sendReceipt(); + $this->ReceiptSent = SS_Datetime::now()->Rfc2822(); + } + } + } + /** * delete attributes, statuslogs, and payments */ - public function onBeforeDelete() + protected function onBeforeDelete() { - $this->Items()->removeAll(); - $this->Modifiers()->removeAll(); - $this->OrderStatusLogs()->removeAll(); + foreach ($this->Items() as $item) { + $item->delete(); + } + + foreach ($this->Modifiers() as $modifier) { + $modifier->delete(); + } + + foreach ($this->OrderStatusLogs() as $logEntry) { + $logEntry->delete(); + } + + // just remove the payment relations… + // that way payment objects still persist (they might be relevant for book-keeping?) $this->Payments()->removeAll(); + parent::onBeforeDelete(); } @@ -617,4 +735,25 @@ public function debug() return $val; } + + /** + * Provide i18n entities for the order class + * + * @return array + */ + public function provideI18nEntities() + { + $entities = parent::provideI18nEntities(); + + // collect all the payment status values + foreach ($this->dbObject('Status')->enumValues() as $value) { + $key = strtoupper($value); + $entities["Order.STATUS_$key"] = array( + $value, + "Translation of the order status '$value'", + ); + } + + return $entities; + } } diff --git a/code/model/OrderStatusLog.php b/code/model/OrderStatusLog.php index f42859ea8..065137ae2 100644 --- a/code/model/OrderStatusLog.php +++ b/code/model/OrderStatusLog.php @@ -58,15 +58,6 @@ public function canEdit($member = null) return false; } - public function onBeforeSave() - { - if (!$this->isInDB()) { - //TO DO - this does not seem to work - $this->AuthorID = Member::currentUser()->ID; - } - parent::onBeforeSave(); - } - public function populateDefaults() { parent::populateDefaults(); @@ -76,15 +67,21 @@ public function populateDefaults() public function onBeforeWrite() { parent::onBeforeWrite(); - if (!$this->AuthorID && $m = Member::currentUser()) { - $this->AuthorID = $m->ID; + if (!$this->AuthorID && $memberID = Member::currentUserID()) { + $this->AuthorID = $memberID; } if (!$this->Title) { $this->Title = "Order Update"; } + } + + public function validate() + { + $validationResult = parent::validate(); if (!$this->OrderID) { - user_error("there is no order id for Order Status Log", E_USER_NOTICE); + $validationResult->error('there is no order id for Order Status Log'); } + return $validationResult; } public function onAfterWrite() @@ -97,9 +94,9 @@ public function onAfterWrite() protected function updateWithLastInfo() { if ($this->OrderID) { - $logs = DataObject::get('OrderStatusLog', "\"OrderID\" = {$this->OrderID}", "\"Created\" DESC", null, 1); - if ($logs && $logs->Count()) { - $latestLog = $logs->First(); + if ( + $latestLog = OrderStatusLog::get()->filter('OrderID', $this->OrderID)->sort('Created', 'DESC')->first() + ) { $this->DispatchedBy = $latestLog->DispatchedBy; $this->DispatchedOn = $latestLog->DispatchedOn; $this->DispatchTicket = $latestLog->DispatchTicket; diff --git a/code/model/ShopMember.php b/code/model/ShopMember.php index 1a283e3eb..fd413a0cf 100644 --- a/code/model/ShopMember.php +++ b/code/model/ShopMember.php @@ -38,7 +38,7 @@ public function updateCMSFields(FieldList $fields) 'Root.Main', DropdownField::create( 'Country', - _t('Address.COUNTRY', 'Country'), + _t('Address.db_Country', 'Country'), SiteConfig::current_site_config()->getCountriesList() ) ); diff --git a/code/model/ShopPayment.php b/code/model/ShopPayment.php index 64dea6e57..10f5523d8 100644 --- a/code/model/ShopPayment.php +++ b/code/model/ShopPayment.php @@ -1,5 +1,7 @@ 'Order', ); - public function onCaptured($response) + public function onAwaitingAuthorized(ServiceResponse $response) + { + $this->placeOrder(); + } + + public function onAwaitingCaptured(ServiceResponse $response) + { + $this->placeOrder(); + } + + public function onAuthorized(ServiceResponse $response) { - $order = $this->owner->Order(); - if ($order->exists()) { + $this->placeOrder(); + } + + public function onCaptured(ServiceResponse $response) + { + // ensure order is being reloaded from DB, to prevent dealing with stale data! + /** @var Order $order */ + $order = Order::get()->byID($this->owner->OrderID); + if ($order && $order->exists()) { OrderProcessor::create($order)->completePayment(); } } + + protected function placeOrder() + { + // ensure order is being reloaded from DB, to prevent dealing with stale data! + /** @var Order $order */ + $order = Order::get()->byID($this->owner->OrderID); + if ($order && $order->exists()) { + OrderProcessor::create($order)->placeOrder(); + } + } } diff --git a/code/model/ShopPaymentService.php b/code/model/ShopPaymentService.php new file mode 100644 index 000000000..337d94464 --- /dev/null +++ b/code/model/ShopPaymentService.php @@ -0,0 +1,11 @@ +OrderID = $originalPayment->OrderID; + } +} diff --git a/code/model/fieldtypes/CanBeFreeCurrency.php b/code/model/fieldtypes/CanBeFreeCurrency.php index 2793603dc..dbffb4a5f 100644 --- a/code/model/fieldtypes/CanBeFreeCurrency.php +++ b/code/model/fieldtypes/CanBeFreeCurrency.php @@ -8,7 +8,7 @@ class CanBeFreeCurrency extends Currency public function Nice() { if ($this->value == 0) { - return _t("ShopCurrency.FREE", "FREE"); + return _t("ShopCurrency.Free", "FREE"); } return parent::Nice(); } diff --git a/code/model/fieldtypes/I18nDatetime.php b/code/model/fieldtypes/I18nDatetime.php index 7e9353b65..6dc983383 100644 --- a/code/model/fieldtypes/I18nDatetime.php +++ b/code/model/fieldtypes/I18nDatetime.php @@ -15,19 +15,19 @@ class I18nDatetime extends SS_Datetime public function Nice() { if ($this->value) { - return $this->FormatI18N(_t('General.DATETIMEFORMATNICE', '%m/%d/%G %I:%M%p')); + return $this->FormatI18N(_t('Shop.DateTimeFormatNice', '%m/%d/%G %I:%M%p')); } } public function NiceDate() { if ($this->value) { - return $this->FormatI18N(_t('General.DATEFORMATNICE', '%m/%d/%G')); + return $this->FormatI18N(_t('Shop.DateFormatNice', '%m/%d/%G')); } } public function Nice24() { - return date(_t('General.DATETIMEFORMATNICE24', 'd/m/Y H:i'), strtotime($this->value)); + return date(_t('Shop.DateTimeFormatNice24', 'd/m/Y H:i'), strtotime($this->value)); } } diff --git a/code/model/filters/MultiFieldPartialMatchFilter.php b/code/model/filters/MultiFieldPartialMatchFilter.php new file mode 100644 index 000000000..a1f270773 --- /dev/null +++ b/code/model/filters/MultiFieldPartialMatchFilter.php @@ -0,0 +1,124 @@ +setValue($value); + } + $this->setSubfilters($otherFields); + } + + /** + * @param array $modifiers + * + * @throws InvalidArgumentException + */ + public function setModifiers(array $modifiers) + { + $modifiers = array_map('strtolower', $modifiers); + + if (($extras = array_diff($modifiers, array('not', 'nocase', 'case', 'splitwords'))) != array()) { + throw new InvalidArgumentException( + get_class($this) . ' does not accept ' . implode(', ', $extras) . ' as modifiers' + ); + } + + $this->modifiers = $modifiers; + $this->subfilterModifiers = array_filter( + $modifiers, + function ($v) { + return $v != 'splitwords'; + } + ); + + if (count($this->subfilters) > 0) { + foreach ($this->subfilters as $f) { + $f->setModifiers($this->subfilterModifiers); + } + } + } + + /** + * @param array $fieldNames + */ + public function setSubfilters(array $fieldNames) + { + $this->subfilters = array(); + if (count($fieldNames) > 0) { + foreach ($fieldNames as $name) { + $this->subfilters[] = new PartialMatchFilter($name, $this->value, $this->subfilterModifiers); + } + } + } + + /** + * @param string $value + */ + public function setValue($value) + { + if ($this->shouldSplitWords() && is_string($value) && preg_match('/\s+/', $value)) { + $value = preg_split('/\s+/', trim($value)); + } + + parent::setValue($value); + + if (count($this->subfilters) > 0) { + foreach ($this->subfilters as $f) { + $f->setValue($value); + } + } + } + + /** + * @return bool + */ + protected function shouldSplitWords() + { + $modifiers = $this->getModifiers(); + return in_array('splitwords', $modifiers); + } + + /** + * @param DataQuery $query + * + * @return $this|DataQuery + */ + public function apply(DataQuery $query) + { + $orGroup = $query->disjunctiveGroup(); + $orGroup = parent::apply($orGroup); + + if (count($this->subfilters) > 0) { + foreach ($this->subfilters as $f) { + $orGroup = $f->apply($orGroup); + } + } + + // The original query will have been affected by the things added to $orGroup above + // but returning this instead of that will cause new filters to be added as AND + return $query; + } +} diff --git a/code/modifiers/shipping/FreeShippingModifier.php b/code/modifiers/shipping/FreeShippingModifier.php index cc65262bd..919d9c9fb 100644 --- a/code/modifiers/shipping/FreeShippingModifier.php +++ b/code/modifiers/shipping/FreeShippingModifier.php @@ -11,6 +11,6 @@ public function eligable() public function TableValue() { - return _t("FreeShippingModifier.FREE", "FREE"); + return _t("FreeShippingModifier.Free", "FREE"); } } diff --git a/code/modifiers/shipping/SimpleShippingModifier.php b/code/modifiers/shipping/SimpleShippingModifier.php index e4c2884a9..f691877f9 100644 --- a/code/modifiers/shipping/SimpleShippingModifier.php +++ b/code/modifiers/shipping/SimpleShippingModifier.php @@ -29,7 +29,12 @@ public function TableTitle() { if ($country = $this->Country()) { $countryList = SiteConfig::current_site_config()->getCountriesList(); - return sprintf(_t("SimpleShippingModifier.SHIPTO", "Ship to %s"), $countryList[$country]); + return _t( + 'SimpleShippingModifier.ShipToCountry', + 'Ship to {Country}', + '', + array('Country' => $countryList[$country]) + ); } else { return parent::TableTitle(); } diff --git a/code/modifiers/shipping/WeightShippingModifier.php b/code/modifiers/shipping/WeightShippingModifier.php index f9aad8746..9ec7dbf94 100644 --- a/code/modifiers/shipping/WeightShippingModifier.php +++ b/code/modifiers/shipping/WeightShippingModifier.php @@ -30,15 +30,6 @@ class WeightShippingModifier extends ShippingModifier protected $weight = 0; - /** - * @deprecated 2.0 please use the SilverStripe config API to set these values (eg. via YAML) - * @param array $costs costs as associative array where the keys are values in kg, and values are the matching price - */ - public static function set_weight_costs($costs) - { - Config::inst()->update('WeightShippingModifier', 'weight_cost', $costs); - } - /** * Calculates shipping cost based on Product Weight. */ @@ -70,7 +61,12 @@ public function value($subtotal = 0) public function TableTitle() { - return sprintf(_t("WeightShippingModifier.TABLETITLE", "Shipping (%f kg)"), $this->Weight()); + return _t( + 'WeightShippingModifier.TableTitle', + 'Shipping ({Kilograms} kg)', + '', + array('Kilograms' => $this->Weight()) + ); } /** diff --git a/code/modifiers/tax/GlobalTaxModifier.php b/code/modifiers/tax/GlobalTaxModifier.php index 6b9ca48b5..e80f2294a 100644 --- a/code/modifiers/tax/GlobalTaxModifier.php +++ b/code/modifiers/tax/GlobalTaxModifier.php @@ -39,6 +39,6 @@ public function TableTitle() { $country = $this->Country ? " for " . $this->Country . " " : ""; return parent::TableTitle() . $country . - ($this->Type == "Chargable" ? '' : _t("GlobalTaxModifier.INCLUDED", ' (included in the above price)')); + ($this->Type == "Chargable" ? '' : _t("GlobalTaxModifier.Included", ' (included in the above price)')); } } diff --git a/code/modifiers/tax/TaxModifier.php b/code/modifiers/tax/TaxModifier.php index a70b28ec2..fee46774d 100644 --- a/code/modifiers/tax/TaxModifier.php +++ b/code/modifiers/tax/TaxModifier.php @@ -21,7 +21,12 @@ public function TableTitle() { $title = parent::TableTitle(); if ($this->Rate) { - $title .= " " . sprintf(_t("TaxModifier.ATRATE", "@ %s"), number_format($this->Rate * 100, 1) . "%"); + $title .= ' ' . _t( + 'TaxModifier.AtRate', + '@ {Rate}%', + '', + array('Rate' => number_format($this->Rate * 100, 1)) + ); } return $title; } diff --git a/code/product/AddProductForm.php b/code/product/AddProductForm.php index ba3687045..f74f3666b 100644 --- a/code/product/AddProductForm.php +++ b/code/product/AddProductForm.php @@ -102,10 +102,10 @@ protected function getFormFields() $count++; } - $fields->push(DropdownField::create('Quantity', _t('AddProductForm.Quantity', 'Quantity'), $values, 1)); + $fields->push(DropdownField::create('Quantity', _t('Shop.Quantity', 'Quantity'), $values, 1)); } else { $fields->push( - NumericField::create('Quantity', _t('AddProductForm.Quantity', 'Quantity'), 1) + NumericField::create('Quantity', _t('Shop.Quantity', 'Quantity'), 1) ->setAttribute('type', 'number') ->setAttribute('min', '0') ); @@ -120,7 +120,7 @@ protected function getFormFields() protected function getFormActions() { return FieldList::create( - FormAction::create('addtocart', _t("AddProductForm.ADDTOCART", 'Add to Cart')) + FormAction::create('addtocart', _t("Product.AddToCart", 'Add to Cart')) ); } diff --git a/code/product/Product.php b/code/product/Product.php index f7f6a398b..cf49e0569 100644 --- a/code/product/Product.php +++ b/code/product/Product.php @@ -19,14 +19,14 @@ class Product extends Page implements Buyable 'InternalItemID' => 'Varchar(30)', //ie SKU, ProductID etc (internal / existing recognition of product) 'Model' => 'Varchar(30)', - 'CostPrice' => 'Currency', // Wholesale cost of the product to the merchant - 'BasePrice' => 'Currency', // Base retail price the item is marked at. + 'CostPrice' => 'Currency(19,4)', // Wholesale cost of the product to the merchant + 'BasePrice' => 'Currency(19,4)', // Base retail price the item is marked at. //physical properties - 'Weight' => 'Float', - 'Height' => 'Float', - 'Width' => 'Float', - 'Depth' => 'Float', + 'Weight' => 'Decimal(12,5)', + 'Height' => 'Decimal(12,5)', + 'Width' => 'Decimal(12,5)', + 'Depth' => 'Decimal(12,5)', 'Featured' => 'Boolean', 'AllowPurchase' => 'Boolean', @@ -111,22 +111,22 @@ public function getCMSFields() self::disableCMSFieldsExtensions(); $fields = parent::getCMSFields(); $fields->fieldByName('Root.Main.Title') - ->setTitle(_t('Product.PAGETITLE', 'Product Title')); + ->setTitle(_t('Product.PageTitle', 'Product Title')); //general fields $fields->addFieldsToTab( 'Root.Main', array( - TextField::create('InternalItemID', _t('Product.CODE', 'Product Code/SKU'), '', 30), - DropdownField::create('ParentID', _t("Product.CATEGORY", "Category"), $this->getCategoryOptions()) - ->setDescription(_t("Product.CATEGORYDESCRIPTION", "This is the parent page or default category.")), + TextField::create('InternalItemID', _t('Product.InternalItemID', 'Product Code/SKU'), '', 30), + DropdownField::create('ParentID', _t("Product.Category", "Category"), $this->getCategoryOptions()) + ->setDescription(_t("Product.CategoryDescription", "This is the parent page or default category.")), ListBoxField::create( 'ProductCategories', - _t("Product.ADDITIONALCATEGORIES", "Additional Categories"), + _t("Product.AdditionalCategories", "Additional Categories"), $this->getCategoryOptionsNoParent() )->setMultiple(true), - TextField::create('Model', _t('Product.MODEL', 'Model'), '', 30), - CheckboxField::create('Featured', _t('Product.FEATURED', 'Featured Product')), - CheckboxField::create('AllowPurchase', _t('Product.ALLOWPURCHASE', 'Allow product to be purchased'), 1), + TextField::create('Model', _t('Product.Model', 'Model'), '', 30), + CheckboxField::create('Featured', _t('Product.Featured', 'Featured Product')), + CheckboxField::create('AllowPurchase', _t('Product.AllowPurchase', 'Allow product to be purchased'), 1), ), 'Content' ); @@ -134,39 +134,44 @@ public function getCMSFields() $fields->addFieldsToTab( 'Root.Pricing', array( - TextField::create('BasePrice', _t('Product.PRICE', 'Price')) - ->setDescription(_t('Product.PRICEDESC', "Base price to sell this product at.")) + TextField::create('BasePrice', _t('Product.db_BasePrice', 'Price')) + ->setDescription(_t('Product.PriceDesc', "Base price to sell this product at.")) ->setMaxLength(12), - TextField::create('CostPrice', _t('Product.COSTPRICE', 'Cost Price')) - ->setDescription(_t('Product.COSTPRICEDESC', 'Wholesale price before markup.')) + TextField::create('CostPrice', _t('Product.db_CostPrice', 'Cost Price')) + ->setDescription(_t('Product.CostPriceDescription', 'Wholesale price before markup.')) ->setMaxLength(12), ) ); //physical measurements + $fieldSubstitutes = array( + 'LengthUnit' => self::config()->length_unit + ); $fields->addFieldsToTab( 'Root.Shipping', array( TextField::create( 'Weight', - sprintf(_t('Product.WEIGHT', 'Weight (%s)'), self::config()->weight_unit), + _t('Product.WeightWithUnit', 'Weight ({WeightUnit})', '', array( + 'WeightUnit' => self::config()->weight_unit + )), '', 12 ), TextField::create( 'Height', - sprintf(_t('Product.HEIGHT', 'Height (%s)'), self::config()->length_unit), + _t('Product.HeightWithUnit', 'Height ({LengthUnit})', '', $fieldSubstitutes), '', 12 ), TextField::create( 'Width', - sprintf(_t('Product.WIDTH', 'Width (%s)'), self::config()->length_unit), + _t('Product.WidthWithUnit', 'Width ({LengthUnit})', '', $fieldSubstitutes), '', 12 ), TextField::create( 'Depth', - sprintf(_t('Product.DEPTH', 'Depth (%s)'), self::config()->length_unit), + _t('Product.DepthWithUnit', 'Depth ({LengthUnit})', '', $fieldSubstitutes), '', 12 ), @@ -175,7 +180,7 @@ public function getCMSFields() if (!$fields->dataFieldByName('Image')) { $fields->addFieldToTab( 'Root.Images', - UploadField::create('Image', _t('Product.IMAGE', 'Product Image')) + UploadField::create('Image', _t('Product.Image', 'Product Image')) ); } self::enableCMSFieldsExtensions(); @@ -264,13 +269,11 @@ public function getCategories() * - if variations, then one of them needs to be purchasable * - if not variations, selling price must be above 0 * - * Other conditions may be added by decorating with the canPurcahse function + * Other conditions may be added by decorating with the canPurchase function * * @param Member $member * @param int $quantity * - * @throws ShopBuyableException - * * @return boolean */ public function canPurchase($member = null, $quantity = 1) @@ -281,33 +284,17 @@ public function canPurchase($member = null, $quantity = 1) } $allowpurchase = false; $extension = self::has_extension("ProductVariationsExtension"); - if ($extension && ProductVariation::get()->filter("ProductID", $this->ID)->first() - ) { + if ($extension && ProductVariation::get()->filter("ProductID", $this->ID)->first()) { foreach ($this->Variations() as $variation) { - // TODO: 2.0, Remove exception handling - try { - if ($variation->canPurchase($member, $quantity)) { - $allowpurchase = true; - - break; - } - } catch (ShopBuyableException $e) { + if ($variation->canPurchase($member, $quantity)) { + $allowpurchase = true; + break; } } - - // if not allowed to buy after any variations then raise the last - // exception again - if (!$allowpurchase && isset($e)) { - Deprecation::notice('2.0', 'Throwing exceptions from within canPurchase will be removed'); - throw $e; - - return false; - } } else { - if ($this->sellingPrice() > 0 || self::config()->allow_zero_price) { - $allowpurchase = true; - } + $allowpurchase = ($this->sellingPrice() > 0 || self::config()->allow_zero_price); } + // Standard mechanism for accepting permission changes from decorators $permissions = $this->extend('canPurchase', $member, $quantity); $permissions[] = $allowpurchase; diff --git a/code/product/ProductCategory.php b/code/product/ProductCategory.php index 6f17aa2f7..808ea919e 100644 --- a/code/product/ProductCategory.php +++ b/code/product/ProductCategory.php @@ -7,7 +7,7 @@ * * @package shop */ -class ProductCategory extends Page +class ProductCategory extends Page implements i18nEntityProvider { private static $belongs_many_many = array( 'Products' => 'Product', @@ -56,7 +56,15 @@ public function ProductsShowable($recursive = true) ) ); if (self::config()->must_have_price) { - $products = $products->filter("BasePrice:GreaterThan", 0); + if (Product::has_extension('ProductVariationsExtension')) { + $products = $products->filterAny(array( + "BasePrice:GreaterThan" => 0, + "Variations.Price:GreaterThan" => 0 + )); + } else { + $products = $products->filter("BasePrice:GreaterThan", 0); + } + } $this->extend('updateProductsShowable', $products); @@ -129,6 +137,21 @@ public function NestedTitle($level = 10, $separator = " > ", $field = "MenuTitle } return implode($separator, array_reverse($parts)); } + + public function provideI18nEntities() + { + $entities = parent::provideI18nEntities(); + + // add the sort option keys + foreach ($this->config()->sort_options as $key => $value) { + $entities["ProductCategory.$key"] = array( + $key, + "Sort by the '$value' field", + ); + } + + return $entities; + } } class ProductCategory_Controller extends Page_Controller diff --git a/code/product/ProductImage.php b/code/product/ProductImage.php index dbbb3e081..7d0181422 100644 --- a/code/product/ProductImage.php +++ b/code/product/ProductImage.php @@ -7,43 +7,90 @@ */ class Product_Image extends DataExtension { - public function getThumbnail() + /** @var Image */ + protected $owner; + + /** + * @param bool $upscale [optional] + * @return Image + */ + public function getThumbnail($upscale = false) { $width = self::config()->thumbnail_width; $height = self::config()->thumbnail_height; - return $this->getImageAt($width, $height); + return $this->getImageAt($width, $height, $upscale); } - public function getContentImage() + /** + * @param bool $upscale [optional] + * @return Image + */ + public function getContentImage($upscale = false) { $width = self::config()->content_image_width; $height = self::config()->content_image_height; - return $this->getImageAt($width, $height); + return $this->getImageAt($width, $height, $upscale); } - public function getLargeImage() + /** + * @param bool $upscale [optional] + * @return Image + */ + public function getLargeImage($upscale = false) { $width = self::config()->large_image_width; $height = self::config()->large_image_height; - return $this->getImageAt($width, $height); + return $this->getImageAt($width, $height, $upscale); } - public function getImageAt($width = null, $height = null) + /** + * Resizes image by width or height only if the source image is bigger than the given width/height. + * This prevents ugly upscaling. + * + * @param int $width [optional] + * @param int $height [optional] + * @param bool $upscale [optional] + * + * @return Image + */ + public function getImageAt($width = null, $height = null, $upscale = false) { + if (!$this->owner->exists()) { + return $this->owner; + } + + $realWidth = $this->owner->getWidth(); + $realHeight = $this->owner->getHeight(); + if ($width && $height) { - return $this->owner->SetSize($width, $height); + return $realWidth < $width && $realHeight < $height && !$upscale + ? $this->owner + : $this->owner->Fit($width, $height); } else { if ($width) { - return $this->owner->SetWidth($width); + return $realWidth < $width && !$upscale + ? $this->owner + : $this->owner->ScaleWidth($width); } else { - return $this->owner->SetHeight($height); + return $realHeight < $height && !$upscale + ? $this->owner + : $this->owner->ScaleWidth($height); } } } + /** + * @return bool - is the image large enough that a "large" image makes sense? + */ + public function HasLargeImage() + { + $imageWidth = intval($this->owner->getWidth()); + return $imageWidth > self::config()->content_image_width; + } + public static function config() { return new Config_ForClass("Product_Image"); diff --git a/code/product/variations/ProductAttributeType.php b/code/product/variations/ProductAttributeType.php index 362c3aeda..48aae9f56 100644 --- a/code/product/variations/ProductAttributeType.php +++ b/code/product/variations/ProductAttributeType.php @@ -73,7 +73,7 @@ public function getCMSFields() LiteralField::create( "Values", '

' . - _t('ProductAttributeType.SAVE_FIRST_MESSAGE', 'Save first, then you can add values.') . + _t('ProductAttributeType.SaveFirstInfo', 'Save first, then you can add values.') . '

' ) ); diff --git a/code/product/variations/ProductVariation.php b/code/product/variations/ProductVariation.php index f8a8ec4d1..4fd90d321 100755 --- a/code/product/variations/ProductVariation.php +++ b/code/product/variations/ProductVariation.php @@ -14,52 +14,52 @@ class ProductVariation extends DataObject implements Buyable { private static $db = array( 'InternalItemID' => 'Varchar(30)', - 'Price' => 'Currency', + 'Price' => 'Currency(19,4)', //physical properties - 'Weight' => 'Float', - 'Height' => 'Float', - 'Width' => 'Float', - 'Depth' => 'Float', + 'Weight' => 'Decimal(12,5)', + 'Height' => 'Decimal(12,5)', + 'Width' => 'Decimal(12,5)', + 'Depth' => 'Decimal(12,5)' ); private static $has_one = array( 'Product' => 'Product', - 'Image' => 'Image', + 'Image' => 'Image' ); private static $many_many = array( - 'AttributeValues' => 'ProductAttributeValue', + 'AttributeValues' => 'ProductAttributeValue' ); private static $casting = array( 'Title' => 'Text', - 'Price' => 'Currency', + 'Price' => 'Currency' ); private static $versioning = array( - 'Live', + 'Live' ); private static $extensions = array( - "Versioned('Live')", + "Versioned('Live')" ); private static $summary_fields = array( 'InternalItemID' => 'Product Code', //'Product.Title' => 'Product', 'Title' => 'Variation', - 'Price' => 'Price', + 'Price' => 'Price' ); private static $searchable_fields = array( 'Product.Title', - 'InternalItemID', + 'InternalItemID' ); private static $indexes = array( 'InternalItemID' => true, - 'LastEdited' => true, + 'LastEdited' => true ); private static $singular_name = "Variation"; @@ -79,8 +79,8 @@ class ProductVariation extends DataObject implements Buyable public function getCMSFields() { $fields = FieldList::create( - TextField::create('InternalItemID', _t('Product.CODE', 'Product Code')), - TextField::create('Price', _t('Product.PRICE', 'Price')) + TextField::create('InternalItemID', _t('Product.Code', 'Product Code')), + TextField::create('Price', _t('Product.db_BasePrice', 'Price')) ); //add attributes dropdowns $attributes = $this->Product()->VariationAttributeTypes(); @@ -97,7 +97,7 @@ public function getCMSFields() 'novalues' . $attribute->Name, '

' . _t( - 'ProductVariation.NO_ATTRIBUTE_VALUES_MESSAGE', + 'ProductVariation.NoAttributeValuesMessage', '{attribute} has no values to choose from. You can create them in the "Products" > "Product Attribute Type" section of the CMS.', 'Warning that will be shown if an attribute doesn\'t have any values', array('attribute' => $attribute->Name) @@ -114,7 +114,7 @@ public function getCMSFields() 'savefirst', '

' . _t( - 'ProductVariation.SAVE_FIRST_MESSAGE', + 'ProductVariation.MustSaveFirstMessage', "You can choose variation attributes after saving for the first time, if they exist." ) . '

' @@ -122,38 +122,48 @@ public function getCMSFields() ); } $fields->push( - UploadField::create('Image', _t('Product.IMAGE', 'Product Image')) + UploadField::create('Image', _t('Product.Image', 'Product Image')) + ); + + //physical measurement units + $fieldSubstitutes = array( + 'LengthUnit' => Product::config()->length_unit ); //physical measurements $fields->push( TextField::create( 'Weight', - sprintf(_t('Product.WEIGHT', 'Weight (%s)'), Product::config()->weight_unit), + _t('Product.WeightWithUnit', 'Weight ({WeightUnit})', '', array( + 'WeightUnit' => Product::config()->weight_unit + )), '', 12 ) ); + $fields->push( TextField::create( 'Height', - sprintf(_t('Product.HEIGHT', 'Height (%s)'), Product::config()->length_unit), + _t('Product.HeightWithUnit', 'Height ({LengthUnit})', '', $fieldSubstitutes), '', 12 ) ); + $fields->push( TextField::create( 'Width', - sprintf(_t('Product.WIDTH', 'Width (%s)'), Product::config()->length_unit), + _t('Product.WidthWithUnit', 'Width ({LengthUnit})', '', $fieldSubstitutes), '', 12 ) ); + $fields->push( TextField::create( 'Depth', - sprintf(_t('Product.DEPTH', 'Depth (%s)'), Product::config()->length_unit), + _t('Product.DepthWithUnit', 'Depth ({LengthUnit})', '', $fieldSubstitutes), '', 12 ) @@ -267,9 +277,6 @@ public function createItem($quantity = 1, $filter = array()) public function sellingPrice() { $price = $this->Price; - if ($price == 0 && ($parentProduct = $this->Product())) { - $price = $parentProduct->sellingPrice(); - } $this->extend("updateSellingPrice", $price); //prevent negative values diff --git a/code/product/variations/ProductVariationsExtension.php b/code/product/variations/ProductVariationsExtension.php index 9ad956b22..4d7866778 100644 --- a/code/product/variations/ProductVariationsExtension.php +++ b/code/product/variations/ProductVariationsExtension.php @@ -26,18 +26,18 @@ public function updateCMSFields(FieldList $fields) array( ListboxField::create( "VariationAttributeTypes", - _t('ProductVariationsExtension.ATTRIBUTES', "Attributes"), + _t('ProductVariationsExtension.Attributes', "Attributes"), ProductAttributeType::get()->map("ID", "Title")->toArray() )->setMultiple(true) ->setDescription( _t( - 'ProductVariationsExtension.ATTRIBUTES_DESCRIPTION', + 'ProductVariationsExtension.AttributesDescription', "These are fields to indicate the way(s) each variation varies. Once selected, they can be edited on each variation." ) ), GridField::create( "Variations", - _t('ProductVariationsExtension.VARIATIONS', "Variations"), + _t('ProductVariationsExtension.Variations', "Variations"), $this->owner->Variations(), GridFieldConfig_RecordEditor::create() ), @@ -49,7 +49,7 @@ public function updateCMSFields(FieldList $fields) LabelField::create( 'variationspriceinstructinos', _t( - 'ProductVariationsExtension.VARIATIONS_INSTRUCTIONS', + 'ProductVariationsExtension.VariationsInfo', "Price - Because you have one or more variations, the price can be set in the \"Variations\" tab." ) ) @@ -63,9 +63,15 @@ public function updateCMSFields(FieldList $fields) public function PriceRange() { $variations = $this->owner->Variations(); + + if (!Product::config()->allow_zero_price) { + $variations = $variations->filter('Price:GreaterThan', 0); + } + if (!$variations->exists() || !$variations->Count()) { return null; } + $prices = $variations->map('ID', 'SellingPrice')->toArray(); $pricedata = array( 'HasRange' => false, @@ -102,6 +108,7 @@ public function getVariationByAttributes(array $attributes) $keyattributes = array_keys($attributes); $id = $keyattributes[0]; $variations = ProductVariation::get()->filter("ProductID", $this->owner->ID); + foreach ($attributes as $typeid => $valueid) { if (!is_numeric($typeid) || !is_numeric($valueid)) { return null; @@ -182,7 +189,7 @@ public function possibleValuesForAttributeType($type) return null; } - return ProductAttributeValue::get() + $list = ProductAttributeValue::get() ->innerJoin( "ProductVariation_AttributeValues", "\"ProductAttributeValue\".\"ID\" = \"ProductVariation_AttributeValues\".\"ProductAttributeValueID\"" @@ -190,6 +197,11 @@ public function possibleValuesForAttributeType($type) "ProductVariation", "\"ProductVariation_AttributeValues\".\"ProductVariationID\" = \"ProductVariation\".\"ID\"" )->where("TypeID = $type AND \"ProductVariation\".\"ProductID\" = " . $this->owner->ID); + + if (!Product::config()->allow_zero_price) { + $list = $list->where('"ProductVariation"."Price" > 0'); + } + return $list; } /** diff --git a/code/product/variations/VariationForm.php b/code/product/variations/VariationForm.php index 6e178f162..b4151affd 100644 --- a/code/product/variations/VariationForm.php +++ b/code/product/variations/VariationForm.php @@ -19,7 +19,7 @@ public function __construct($controller, $name = "VariationForm") foreach ($attributes as $attribute) { $attributeDropdown = $attribute->getDropDownField( _t( - 'VariationForm.CHOOSE_ATTRIBUTE', + 'VariationForm.ChooseAttribute', "Choose {attribute} …", '', array('attribute' => $attribute->Label) @@ -37,13 +37,17 @@ public function __construct($controller, $name = "VariationForm") if (self::$include_json) { $vararray = array(); - + $query = $query2 = new SQLQuery(); - + $query->setSelect('ID') ->setFrom('ProductVariation') - ->setWhere(array('ProductID' => $product->ID)); - + ->addWhere(array('ProductID' => $product->ID)); + + if (!Product::config()->allow_zero_price) { + $query->addWhere('"Price" > 0'); + } + foreach ($query->execute()->column('ID') as $variationID) { $query2->setSelect('ProductAttributeValueID') ->setFrom('ProductVariation_AttributeValues') @@ -100,7 +104,7 @@ public function addtocart($data, $form) try { $success = $variation->canPurchase(null, $data['Quantity']); - } catch (ShopBuyableException $e) { + } catch (Exception $e) { $message = get_class($e); // added hook to update message $this->extend('updateVariationAddToCartMessage', $e, $message, $variation); @@ -119,7 +123,7 @@ public function addtocart($data, $form) if ($cart->add($variation, $quantity)) { $form->sessionMessage( - _t('ShoppingCart.ITEMADD', "Item has been added successfully."), + _t('ShoppingCart.ItemAdded', "Item has been added successfully."), "good" ); } else { @@ -128,7 +132,7 @@ public function addtocart($data, $form) } else { $variation = null; $form->sessionMessage( - _t('VariationForm.VARIATION_NOT_AVAILABLE', "That variation is not available, sorry."), + _t('VariationForm.VariationNotAvailable', "That variation is not available, sorry."), "bad" ); //validation fail } @@ -142,7 +146,7 @@ public function addtocart($data, $form) public function getBuyable($data = null) { if (isset($data['ProductAttributes']) - && $variation = $this->Controller()->getVariationByAttributes($data['ProductAttributes']) + && $variation = $this->getController()->getVariationByAttributes($data['ProductAttributes']) ) { return $variation; } diff --git a/code/product/variations/VariationFormValidator.php b/code/product/variations/VariationFormValidator.php index ba15ce368..b360419dd 100644 --- a/code/product/variations/VariationFormValidator.php +++ b/code/product/variations/VariationFormValidator.php @@ -12,7 +12,7 @@ public function php($data) if ($valid && !$this->form->getBuyable($_POST)) { $this->validationError( "", - _t('VariationForm.PRODUCT_NOT_AVAILABLE', "This product is not available with the selected options.") + _t('VariationForm.ProductNotAvailable', "This product is not available with the selected options.") ); $valid = false; diff --git a/code/reports/ShopPeriodReport.php b/code/reports/ShopPeriodReport.php index 8c94f302e..8dc77e5bf 100644 --- a/code/reports/ShopPeriodReport.php +++ b/code/reports/ShopPeriodReport.php @@ -4,7 +4,7 @@ * Base class for creating reports that can be filtered to a specific range. * Record grouping is also supported. */ -class ShopPeriodReport extends SS_Report +class ShopPeriodReport extends SS_Report implements i18nEntityProvider { private static $display_uncategorised_data = false; @@ -24,12 +24,12 @@ class ShopPeriodReport extends SS_Report public function title() { - return _t($this->class . ".TITLE", $this->title); + return _t($this->class . ".Title", $this->title); } public function description() { - return _t($this->class . ".DESCRIPTION", $this->description); + return _t($this->class . ".Description", $this->description); } public function parameterFields() @@ -168,6 +168,25 @@ protected function fd($date, $format) { return DB::getConn()->formattedDatetimeClause($date, $format); } + + /** + * Provide translatable entities for this class and all subclasses + * + * @return array + */ + public function provideI18nEntities() + { + return array( + "{$this->class}.Title" => array( + $this->title, + "Title for the {$this->class} report", + ), + "{$this->class}.Description" => array( + $this->description, + "Description for the {$this->class} report", + ), + ); + } } class ShopReport_Query extends SQLQuery diff --git a/code/reports/ShopSideReport.php b/code/reports/ShopSideReport.php index df75855e9..e0f38236e 100644 --- a/code/reports/ShopSideReport.php +++ b/code/reports/ShopSideReport.php @@ -13,12 +13,12 @@ class ShopSideReport_FeaturedProducts extends SS_Report { public function title() { - return _t('ShopSideReport.FEATUREDPRODUCTS', "Featured Products"); + return _t('ShopSideReport.FeaturedProducts', "Featured Products"); } public function group() { - return _t('ShopSideReport.ShopGROUP', "Shop"); + return _t('ShopSideReport.ShopGroup', "Shop"); } public function sort() @@ -51,12 +51,12 @@ class ShopSideReport_AllProducts extends SS_Report { public function title() { - return _t('ShopSideReport.ALLPRODUCTS', "All Products"); + return _t('ShopSideReport.AllProducts', "All Products"); } public function group() { - return _t('ShopSideReport.ShopGROUP', "Shop"); + return _t('ShopSideReport.ShopGroup', "Shop"); } public function sort() @@ -84,12 +84,12 @@ class ShopSideReport_NoImageProducts extends SS_Report { public function title() { - return _t('ShopSideReport.NOIMAGE', "Products with no image"); + return _t('ShopSideReport.NoImage', "Products with no image"); } public function group() { - return _t('ShopSideReport.ShopGROUP', "Shop"); + return _t('ShopSideReport.ShopGroup', "Shop"); } public function sort() @@ -119,12 +119,12 @@ class ShopSideReport_HeavyProducts extends SS_Report { public function title() { - return _t('ShopSideReport.HEAVY', "Heavy Products"); + return _t('ShopSideReport.Heavy', "Heavy Products"); } public function group() { - return _t('ShopSideReport.ShopGROUP', "Shop"); + return _t('ShopSideReport.ShopGroup', "Shop"); } public function sort() @@ -140,7 +140,7 @@ public function sourceRecords($params = null) public function columns() { return array( - "Title" => array( + "Title" => array( "title" => "Title", "link" => true, ), diff --git a/composer.json b/composer.json index 26f0c6a17..fa992ce98 100644 --- a/composer.json +++ b/composer.json @@ -12,15 +12,19 @@ } ], "require" : { + "php": ">=5.5", "silverstripe/cms": "~3.1", "silverstripe/framework": "~3.1", - "silverstripe/silverstripe-omnipay" : "~1.1", + "silverstripe/silverstripe-omnipay" : "~2.0", "icecaster/versioned-gridfield": "~1.0", "burnbright/silverstripe-listsorter": "~2.0", - "burnbright/silverstripe-sqlquerylist": "~1.0" + "burnbright/silverstripe-sqlquerylist": "~1.0", + "markguinn/silverstripe-email-helpers": "~1.2.0" }, "require-dev": { "guzzle/plugin-mock": "~3.1", + "omnipay/dummy": "~2.1", + "omnipay/paymentexpress": "~2.1", "phpunit/phpunit": "~3.7" }, "replace": { @@ -49,6 +53,5 @@ "forum" : "http://www.silverstripe.org/e-commerce-module-forum", "email" : "mark@adaircreative.com", "docs" : "http://docs.ss-shop.org" - }, - "prefer-stable": true + } } diff --git a/css/account.css b/css/account.css index 396fb797b..38832a395 100644 --- a/css/account.css +++ b/css/account.css @@ -25,3 +25,75 @@ table.orderhistory { #ChangePasswordForm_ChangePasswordForm { margin-left: 5%; } + + +/* address book panels ************************************************************/ +.AccountPage h2 { + clear: left; +} + +.address-panel { + position: relative; + float: left; + width: 48%; + background: #eee; + border: 1px solid #ccc; + border-radius:2px; + padding: 20px; + margin-bottom: 5px; +} +.address-panel.odd { + margin-right:2%; +} + +.address-panel .panel-body { + font-size:14px; + line-height:17px; +} +.address-panel .panel-footer { + border-top: 1px solid #d9d9d9; + padding: 8px 20px; + bottom: 0px; + margin: 15px -20px -20px; +} + +/*@include clearfix;*/ +.cf:before, +.cf:after { + content: " "; /* 1 */ + display: table; /* 2 */ +} +.cf:after { + clear: both; +} + +.address-panel .panel-footer .btn { + line-height: 32px; + padding: 0 9px; + font-size: 10px; + margin: 0 9px 0 0; +} + +.address-panel .remove-address { + float: right; + line-height: 32px; + border:none; +} + +.address-panel .remove-address img { + background: none; + border: none; +} + +.address-panel .tag { + position: relative; + top: -21px; + background: #f7931e; + color: white; + font-size: 10px; + padding: 4px 10px; + border-radius: 0 0 2px 2px; + float: right; + right: -10px; + margin-left: 10px; +} diff --git a/docs/en/01_Getting_Set_Up/01_Installation.md b/docs/en/01_Getting_Set_Up/01_Installation.md index 5e055e011..567882173 100644 --- a/docs/en/01_Getting_Set_Up/01_Installation.md +++ b/docs/en/01_Getting_Set_Up/01_Installation.md @@ -1,8 +1,10 @@ In a terminal window, in the website root, type: + ```sh -composer require burnbright/silverstripe-shop +composer require silvershop/core ``` -Then in a browser `{yoursite.com}/dev/build`. + +Then in a browser `{yoursite.com}/dev/build`. ## Demo in Localhost diff --git a/docs/en/01_Getting_Set_Up/02_Upgrading.md b/docs/en/01_Getting_Set_Up/02_Upgrading.md index 46cb1ef28..9f0984af9 100644 --- a/docs/en/01_Getting_Set_Up/02_Upgrading.md +++ b/docs/en/01_Getting_Set_Up/02_Upgrading.md @@ -3,8 +3,17 @@ This page is intended to make you aware of upgrade issues you may face, and how Don't forget to run the following url commands when you upgrade the shop module: [yourdomain.com]/dev/build?flush=all - [yourdomain.com]/tasks/ShopMigrationTask + [yourdomain.com]/dev/tasks/ShopMigrationTask +# 2.0 + +## Translations no longer appear + +You translated your shop to another language than english, now your translations no longer work? This is, because a lot of the translation keys have changed to be compatible with the new SilverStripe translation syntax. Instead of using `<% _t('KEY','VALUE') %>` calls in templates, one should now use: `<%t KEY 'VALUE' %>` instead. + +It's best to look at the current shop templates and adjust your custom templates accordingly. You can also look at the language files within the `silvershop/lang` folder to get an idea of the current translation keys/values. + +Sadly, there's no automatic task that will fix these issues for you. # 1.0 diff --git a/docs/en/01_Getting_Set_Up/03_Troubleshooting.md b/docs/en/01_Getting_Set_Up/03_Troubleshooting.md index 951fb3b33..7e62b7acc 100644 --- a/docs/en/01_Getting_Set_Up/03_Troubleshooting.md +++ b/docs/en/01_Getting_Set_Up/03_Troubleshooting.md @@ -47,7 +47,11 @@ The default shop module provides a few shipping [modifiers](../03_How_It_Works/O ## How do I use in a different language? Follow the [Silverstripe internationalisation guide](http://docs.silverstripe.org/en/developer_guides/i18n/) -We welcome your contribution of language files. + + +The translation of the silvershop module is being done via Transifex. You can see the current translation progress on: [www.transifex.com/silvershop/silverstripe-shop](https://www.transifex.com/silvershop/silverstripe-shop). +If your language isn't translated yet, we welcome your contribution on transifex! Just click the **"Help Translate "SilverShop"** button to get started. + ## When are cart/order totals recalculated? diff --git a/docs/en/01_Getting_Set_Up/06_Payment.md b/docs/en/01_Getting_Set_Up/06_Payment.md index 1d78063da..9842a01cf 100644 --- a/docs/en/01_Getting_Set_Up/06_Payment.md +++ b/docs/en/01_Getting_Set_Up/06_Payment.md @@ -1,15 +1,15 @@ Online payment is a critical part of any ecommerce solution. This module makes use of the PHP Omnipay payment library, which does a great job of standardising online payments. -This module integrates with the omnipay library via the [SilverStripe-Omnipay module](https://github.com/burnbright/silverstripe-omnipay). It is automatically required by the shop module, via composer. +This module integrates with the omnipay library via the [SilverStripe-Omnipay module](https://github.com/silverstripe/silverstripe-omnipay). It is automatically required by the shop module, via composer. This Omnipay presentation gives a general overview to online payments and integrating omnipay with SilverStripe: http://jeremyshipman.com/blog/my-2c-on-omnipay-integrating-with-silverstripe/ ## Available payment types -There are a number of [Omnipay payment drivers that come with omnipay out of the box](https://github.com/thephpleague/omnipay/tree/1.1#payment-gateways). +There are many [gateways](https://github.com/thephpleague/omnipay#payment-gateways) available, which you can install separately. Note that currently this module uses version 2.x of the Omnipay library. The best way to find additional Omnipay payment drivers is perhaps to do a search on Packagist: https://packagist.org/search/?q=omnipay -Here is a tutorial for [setting up PayPal via omnipay](https://github.com/burnbright/silverstripe-omnipay/blob/master/docs/en/PayPalExpressSetup.md). +Here is a tutorial for [setting up PayPal via Omnipay](https://github.com/silverstripe/silverstripe-omnipay/blob/master/docs/en/PayPalExpressSetup.md). diff --git a/docs/en/01_Getting_Set_Up/index.md b/docs/en/01_Getting_Set_Up/index.md index 589a91fbc..4248fbee6 100644 --- a/docs/en/01_Getting_Set_Up/index.md +++ b/docs/en/01_Getting_Set_Up/index.md @@ -26,7 +26,7 @@ Add some [automated tasks](01_Getting_Set_Up/Tasks.md) to handle some things aut [Products can be bulk loaded](01_Getting_Set_Up/Bulk_Loading.md), saving time on larger websites. ## Testing / Development Environment -Useful development tools are accessible via [yoursite]/dev/shop. +Useful development tools are accessible via `[yoursite]/dev/shop`. ### Debugging @@ -34,7 +34,32 @@ If you are wanting to use a debugger tool, you'll probably need to make sure you ### E-Mails -To catch local emails, you either need to set up a local dummy SMTP server, or... +The best way to catch/debug local emails is to use a service such as [Mailtrap](https://mailtrap.io/) which has a free plan. + +The [silverstripe-email-helpers](https://packagist.org/packages/markguinn/silverstripe-email-helpers) module that will be installed alongside silvershop can be used to send your emails to the mailtrap service. To do so, create a config file `mysite/_config/mailer.yml` with the following content: + +```yaml +--- +Name: shop-mailer +Only: + environment: 'dev' +--- +SmtpMailer: + host: 'mailtrap.io' + user: '' + password: '' + encryption: 'tls' + charset: 'UTF-8' + +Injector: + Mailer: + class: SmtpMailer +``` + +After running `dev/build` your emails should now be sent to mailtrap. + +Alternatively, you can: * Windows - you can run the "Antix SMTP Server For Developers", and open the emails in your preferred email client. - * Linux,Mac - pipe emails to a custom php script, such as [this one](http://blogs.bigfish.tv/adam/2009/12/03/setup-a-testing-mail-server-using-php-on-mac-os-x/). \ No newline at end of file + * Linux,Mac - pipe emails to a custom php script, such as [this one](http://blogs.bigfish.tv/adam/2009/12/03/setup-a-testing-mail-server-using-php-on-mac-os-x/). + diff --git a/docs/en/02_Customisation/01_Recipes/Custom_Checkout.md b/docs/en/02_Customisation/01_Recipes/Custom_Checkout.md index 32d2bb81a..fa8f8ef07 100644 --- a/docs/en/02_Customisation/01_Recipes/Custom_Checkout.md +++ b/docs/en/02_Customisation/01_Recipes/Custom_Checkout.md @@ -1,6 +1,6 @@ The shop module allows the checkout to exist as either a single page, or a multi-page solution, and somewhere in-between. -The multi-page solution is known as `Stepped Checkout`. The single page solution utilises `CheckoutForm`. In both cases, you will need to understand what is `CheckoutComponent` . +The multi-page solution is known as `Stepped Checkout`. The single page solution utilises `CheckoutForm`. In both cases, you will need to understand what is `CheckoutComponent`. ## Checkout Components @@ -20,6 +20,36 @@ The checkout component system was inspired by `GridField`'s components. A single step checkout is somewhat limited, because it requires the use of ajax to modify the form if one part relies on another. +You can modify the components in your checkout by subclassing `CheckoutComponentConfig`. Example where no shipping and billing Addresses are added (eg. for virtual goods): + +```php +class MyCustomCheckoutComponentConfig extends CheckoutComponentConfig +{ + public function __construct(Order $order) + { + parent::__construct($order); + $this->addComponent(CustomerDetailsCheckoutComponent::create()); + if (Checkout::member_creation_enabled() && !Member::currentUserID()) { + $this->addComponent(MembershipCheckoutComponent::create()); + } + if (count(GatewayInfo::getSupportedGateways()) > 1) { + $this->addComponent(PaymentCheckoutComponent::create()); + } + $this->addComponent(NotesCheckoutComponent::create()); + $this->addComponent(TermsCheckoutComponent::create()); + } +} +``` + +You then use the Injector to use your class instead of CheckoutComponentConfig: + +```yaml +# Put this in your config.yml file +Injector: + CheckoutComponentConfig: + class: MyCustomCheckoutComponentConfig +``` + ## Multi Step Checkout See [Multi Step Checkout](Multi_Step_Checkout.md). diff --git a/docs/en/02_Customisation/01_Recipes/Customise_Address.md b/docs/en/02_Customisation/01_Recipes/Customise_Address.md index 8d47fd710..451016dc1 100644 --- a/docs/en/02_Customisation/01_Recipes/Customise_Address.md +++ b/docs/en/02_Customisation/01_Recipes/Customise_Address.md @@ -8,7 +8,8 @@ In `[mysite]/code/ExtendedAddress.php` ```php 'Varchar' - ); - } - +```php + 'Varchar' + ); +} +``` + +To your `config.yml` file, add: -To your _config.php file, add: +```yaml +Member: + extensions: + - ExtendedCustomer - :::php - Object::add_extension('Member','ExtendedCustomer'); - Object::add_extension('Order','ExtendedCustomer'); +Order: + extensions: + - ExtendedCustomer +``` ### Update form(s) @@ -36,21 +43,24 @@ various forms. In [mysite]/code/ExtendedOrderForm.php - :::php - Fields()->fieldByName("LeftOrder")->FieldList(); - $leftfields->insertAfter(new TextField("MyExtraField","My Extra Field"),"Country"); - } - +```php +Fields()->fieldByName("LeftOrder")->FieldList(); + $leftfields->insertAfter(new TextField("MyExtraField","My Extra Field"),"Country"); } +} +``` -To your _config.php file, add: +To your `config.yml` file, add: - :::php - Object::add_extension('OrderForm','ExtendedOrderForm'); +```yaml +OrderForm: + extensions: + - ExtendedOrderForm +```
Note: remember to flush the class manifest by adding ?flush=1 to your site url. diff --git a/docs/en/02_Customisation/01_Recipes/Default_Admin.md b/docs/en/02_Customisation/01_Recipes/Default_Admin.md index ef93b138c..aea1e660b 100644 --- a/docs/en/02_Customisation/01_Recipes/Default_Admin.md +++ b/docs/en/02_Customisation/01_Recipes/Default_Admin.md @@ -1,6 +1,7 @@ # Set orders as the default admin panel Add the following to your yaml config: + ```yml AdminRootController: default_panel: 'OrdersAdmin' diff --git a/docs/en/02_Customisation/01_Recipes/Featured_Products.md b/docs/en/02_Customisation/01_Recipes/Featured_Products.md index 79bea4e2e..52a6d2a75 100644 --- a/docs/en/02_Customisation/01_Recipes/Featured_Products.md +++ b/docs/en/02_Customisation/01_Recipes/Featured_Products.md @@ -6,28 +6,35 @@ You may want to display a small set of featured products somewhere on your site, [mysite]/code/HomePage.php: - :::php - class HomePage extends ProductCategory{ - //... - } - class HomePage_Controller extends ProductCategory_Controller{ - - /** - * Get all products marked as featured that can be purchased. - */ - function FeaturedProducts(){ - $filter = '"Featured" = 1 AND "AllowPurchase" = 1'; - return DataObject::get('Product',$filter); - } - +```php +class HomePage extends ProductCategory +{ + //... +} +class HomePage_Controller extends ProductCategory_Controller +{ + + /** + * Get all products marked as featured that can be purchased. + */ + function FeaturedProducts() + { + return Product::get()->filter(array( + 'Featured' => 1 + 'AllowPurchase' => 1 + )); } +} +``` [mysite]/templates/Layout/HomePage.ss - <% if FeaturedProducts %> -
- <% loop FeaturedProducts %> - <% include ProductGroupItem %> - <% end_loop %> -
- <% end_if %> +``` +<% if FeaturedProducts %> +
+ <% loop FeaturedProducts %> + <% include ProductGroupItem %> + <% end_loop %> +
+<% end_if %> +``` \ No newline at end of file diff --git a/docs/en/02_Customisation/01_Recipes/Multi_Language.md b/docs/en/02_Customisation/01_Recipes/Multi_Language.md index a2cb5a2e6..ebf963dfb 100644 --- a/docs/en/02_Customisation/01_Recipes/Multi_Language.md +++ b/docs/en/02_Customisation/01_Recipes/Multi_Language.md @@ -1,15 +1,6 @@ - - ## Multi Language / Translated Shop Making your shop support multiple languages is much like doing the same for any other SilverStripe website. [Here is a guide](http://www.balbuss.com/setting-up-a-multilingual-site/). -Here is some shop-specific information: - -Add the following to your _config.php file: - - :::php - Object::add_extension('SiteTree', 'Translatable'); - Object::add_extension('SiteConfig', 'Translatable'); - Object::add_extension('ProductVariation', 'Translatable'); +You can use the [Fluent](https://packagist.org/packages/tractorcow/silverstripe-fluent) or [Translatable](https://packagist.org/packages/silverstripe/translatable) Module to translate your shop. diff --git a/docs/en/02_Customisation/01_Recipes/Multi_Step_Checkout.md b/docs/en/02_Customisation/01_Recipes/Multi_Step_Checkout.md index 6c1b5d8ac..f197873f7 100644 --- a/docs/en/02_Customisation/01_Recipes/Multi_Step_Checkout.md +++ b/docs/en/02_Customisation/01_Recipes/Multi_Step_Checkout.md @@ -1,24 +1,26 @@ # Multi-Step "Stepped" Checkout -Historically the checkout has only been based on a single OrderForm. Whilst its nice to place an order in a single form submission, in most cases different parts of the checkout process rely on the data of others. In particular, to provide prices for shipping, we first need an address. To do this in a single form, we require the aid of javascript/ajax. +Historically the checkout has only been based on a single OrderForm. Whilst it's nice to place an order in a single form submission, in most cases different parts of the checkout process rely on the data of others. In particular, to provide prices for shipping, we first need an address. To do this in a single form, we require the aid of javascript/ajax. -There is a multi-step version of the checkout available that can be enabled. See the code in `shop/code/steppedcheckout`, which contains a number of decorators for `CheckoutPage`. Each step is stored in a class with an action to view the step, a form to gather data, and an action to process the form data. +There is a multi-step version of the checkout available that can be enabled. See the code in `silvershop/code/checkout/steps`, which contains a number of decorators for `CheckoutPage`. Each step is stored in a class with an action to view the step, a form to gather data, and an action to process the form data. To enable the multi-step checkout, define the steps in your config.yaml file. The config is a mapping from url `action` to `CheckoutStep`. For example: + ```yaml CheckoutPage: steps: - membership: 'CheckoutStep_Membership' - shippingaddress: 'CheckoutStep_AddressBook' - billingaddress: 'CheckoutStep_AddressBook' - shippingmethod: 'CheckoutStep_ShippingMethod' - summary: 'CheckoutStep_Summary' + membership: 'CheckoutStep_Membership' + contactdetails: 'CheckoutStep_ContactDetails' + shippingaddress: 'CheckoutStep_AddressBook' + billingaddress: 'CheckoutStep_AddressBook' + shippingmethod: 'CheckoutStep_ShippingMethod' + summary: 'CheckoutStep_Summary' ``` - -The above configuration is picked up by the `SteppedCheckout::setupSteps()` function in `shop/_config.php`, and it adds all of the steps as extensions to `CheckoutPage_Controller`, and make sure the index action (yoursite.tld/checkout/) is the first step. - -Next, optionally replace your `CheckoutPage.ss` template with one that uses steps. You can find such a template in `shop/templates/Layout/SteppedCheckoutPage.ss` you could put this in your mysite/templates/Layout folder and rename it to `CheckoutPage.ss`. - + +The above configuration is picked up by the `SteppedCheckout::setupSteps()` function in `silvershop/_config.php`, and it adds all of the steps as extensions to `CheckoutPage_Controller`, and make sure the index action (yoursite.tld/checkout/) is the first step. + +Next, optionally replace your `CheckoutPage.ss` template with one that uses steps. You can find such a template in `silvershop/templates/Layout/SteppedCheckoutPage.ss` you could put this in your mysite/templates/Layout folder and rename it to `CheckoutPage.ss`. + You may notice that the `SteppedCheckoutPage.ss` template contains statements like: ```html @@ -38,6 +40,7 @@ mysite/templates/Layout/CheckoutPage_billingaddress.ss mysite/templates/Layout/CheckoutPage_paymentmethod.ss mysite/templates/Layout/CheckoutPage_summary.ss ``` + (templates could also be in your theme) ## Configuring Steps @@ -46,13 +49,13 @@ Steps can be configured using yaml config: ```yaml CheckoutPage: - steps: - 'membership' : 'CheckoutStep_Membership' - 'contactdetails' : 'CheckoutStep_ContactDetails' - 'shippingaddress' : 'CheckoutStep_Address' - 'billingaddress' : 'CheckoutStep_Address' - 'paymentmethod' : 'CheckoutStep_PaymentMethod' - 'summary' : 'CheckoutStep_Summary' + steps: + 'membership' : 'CheckoutStep_Membership' + 'contactdetails' : 'CheckoutStep_ContactDetails' + 'shippingaddress' : 'CheckoutStep_Address' + 'billingaddress' : 'CheckoutStep_Address' + 'paymentmethod' : 'CheckoutStep_PaymentMethod' + 'summary' : 'CheckoutStep_Summary' ``` Add/remove/reorder steps as you please, but keep in mind that the data from one step may be reliant on data from another. @@ -60,9 +63,10 @@ Add/remove/reorder steps as you please, but keep in mind that the data from one ## Additional Form Fields -When needing additional Form Fields in a Multi-Step Checkout, your best bet is to extend the CheckoutStep class and point this to a custom Checkout Component. Below is an example to demonstrate. Say, we need an Organisation name in the contact details because the eCommerce website will sell products B2B. +When needing additional Form Fields in a Multi-Step Checkout, your best bet is to extend the CheckoutStep class and point this to a custom Checkout Component. Below is an example to demonstrate. Say, we need an Organisation name in the contact details because the eCommerce website will sell products B2B. + +1) Adjust the yaml config: -1) Adjust the yaml config: ```yaml CheckoutPage: steps: @@ -72,8 +76,10 @@ CheckoutPage: ``` 2) As recommended in [Customising_Fields](Custom_Fields), use a Data Extension to extend existing classes. In this example add Organisation to the Member and Order classes. + ```php -class ExtendedCustomer extends DataExtension{ +class ExtendedCustomer extends DataExtension +{ private static $db = array( 'Organisation' => 'Varchar' ); @@ -84,6 +90,7 @@ class ExtendedCustomer extends DataExtension{ ``` In your config.yml file: + ```yaml Member: extensions: @@ -96,21 +103,26 @@ Order: 3) Copy CheckoutStep_ContactDetails.php and save under [mysite]/code as CheckoutStep_ContactDetailsCustom.php. In addition, copy CustomerDetailsCheckoutComponent.php and save under [mysite]/code as CustomerDetailsCheckoutComponentCustom.php. 4) Open CheckoutStep_ContactDetailsCustom.php and rename the class to CheckoutStep_ContactDetailsCustom. Change one line in the ContactDetailsForm function to point to our custom Checkout Component. + ```php $config->addComponent(new CustomerDetailsCheckoutComponentCustom()); ``` 5) Open CustomerDetailsCheckoutComponent.php and rename the class to CustomerDetailsCheckoutComponentCustom. Update two functions as below. -- Add the Organisation to the form fields: + +Add the Organisation to the form fields: + ```php public function getFormFields(Order $order) { $fields = new FieldList( $organisation = TextField::create('Organisation'), ... ``` -- Add Organisation to the getData function: + +Add Organisation to the getData function: + ```php -public function getData(Order $order) { +public function getData(Order $order) { if($order->Organisation || $order->FirstName || $order->Surname || $order->Email){ return array( 'Organisation' => $order->Organisation, @@ -121,4 +133,4 @@ public function getData(Order $order) { ... ``` -6) Flush the class manifest by adding ?flush=1 to your site url. +6) Flush the class manifest by adding ?flush=1 to your site url. diff --git a/docs/en/02_Customisation/01_Recipes/Multiple_Images_Product.md b/docs/en/02_Customisation/01_Recipes/Multiple_Images_Product.md index b7824c67b..b55ce72d0 100644 --- a/docs/en/02_Customisation/01_Recipes/Multiple_Images_Product.md +++ b/docs/en/02_Customisation/01_Recipes/Multiple_Images_Product.md @@ -28,8 +28,8 @@ Add the extension in `_config/config.yml` ```yaml Product: - extensions: - - MultipleProductImages + extensions: + - MultipleProductImages ``` `[mysite]/templates/Includes/AdditionalImages.ss` @@ -42,4 +42,4 @@ Product: <% end_if %> ``` -Add <% include AdditionalImages %> somewhere in your Product.ss template. \ No newline at end of file +Add `<% include AdditionalImages %>` somewhere in your Product.ss template. \ No newline at end of file diff --git a/docs/en/02_Customisation/Contributing.md b/docs/en/02_Customisation/Contributing.md index e6657d785..1e3fba8e8 100644 --- a/docs/en/02_Customisation/Contributing.md +++ b/docs/en/02_Customisation/Contributing.md @@ -4,11 +4,11 @@ This module only moves forward as we each build the features we need. We love pu ### Here is a quick list of ways you can contribute: - * __Test the latest code__. Find out what branch is currently being worked on (usually 'develop'). Install it and try it out. + * __Test the latest code__. Find out what branch is currently being worked on (usually 'master'). Install it and try it out. * __Code new features and bug fixes__. Submit github pull requests. Don't forget to write PHPUnit tests that ensure your code runs. All pull requests are automatically tested [via TravisCI](https://travis-ci.org/burnbright/silverstripe-shop/pull_requests). * __Submit issues and ideas__. These can be bugs, or ideas. Be descriptive and detailed. Its better to discuss and design ideas before writing code. Please first check the [list of existing issues](https://github.com/burnbright/silverstripe-shop/issues) to make sure an identical one doesn't exist already. - * __Write documentation__. Both the developer and user documentation can have pieces missing. Documentation is stored in the repository under `/shop/docs` ,and `/shop/docs_user`. Documentation gets displayed at http://docs.ss-shop.org - * __Provide translations__. This will allow people speaking other languages to use the shop module. + * __Write documentation__. Both the developer and user documentation can have pieces missing. Documentation is stored in the repository under `/shop/docs`, and `/shop/docs_user`. Documentation gets displayed at http://docs.ss-shop.org + * __Provide translations__. This will allow people speaking other languages to use the shop module: https://www.transifex.com/silvershop/silverstripe-shop/ * __Financial contribution__. Giving a donation, or financing the development of some features will help this module go further, faster. If you would like to contribute code, please [fork this project](https://github.com/burnbright/silverstripe-shop). You diff --git a/docs/en/02_Customisation/Emails.md b/docs/en/02_Customisation/Emails.md index 79b5e7a78..8660cfe97 100644 --- a/docs/en/02_Customisation/Emails.md +++ b/docs/en/02_Customisation/Emails.md @@ -3,19 +3,20 @@ Shop emails can be customised to suit your project needs. ## Enable / disable sending of emails There are a few yaml config options that will affect which emails are sent: + ```yaml OrderProcessor: - #send order confirmation when order is placed, but unpaid - send_confirmation: true + #send order confirmation when order is placed, but unpaid + send_confirmation: true #send a bcc copy of emails to administrator OrderEmailNotifier: - bcc_confirmation_to_admin: true - bcc_receipt_to_admin: true + bcc_confirmation_to_admin: true + bcc_receipt_to_admin: true #Specify the 'from' address to use in email correspondence ShopConfig: - email_from: store@website.com + email_from: store@website.com ``` @@ -27,10 +28,13 @@ Update email content by overriding the templates inside the `templates/email/` f Email subjects are in the translation system, so you can do the following to change an email subject line: -mysite/lang/en.yml: + ```yaml +#in mysite/lang/en.yml en: - OrderNotifier: - RECEIPTSUBJECT: 'My Website Order #%d Receipt' + ShopEmail: + ConfirmationSubject: 'My Website Order #{OrderNo} confirmation' + ReceiptSubject: 'My Website Order #{OrderNo} receipt' + CancelSubject: 'My Website Order #{OrderNo} cancelled by member' ``` diff --git a/docs/en/02_Customisation/index.md b/docs/en/02_Customisation/index.md index 0e4074756..08ab85aa5 100644 --- a/docs/en/02_Customisation/index.md +++ b/docs/en/02_Customisation/index.md @@ -32,17 +32,21 @@ Hopefully you don't need to override core SilverStripe, or shop module code, but * Rewrite the file directly. Not recommended if you aren't using a version control system like git. Git allows creating branches of code, which you can use to store your customisations. You should aim to submit these changes back to the project as improvements. * Create Sub-classes. Extend the parent class, and overwrite whatever methods you need to. For example: - MyCustomCategory might extend ProductCategory. Or MyCustomProduct might extend Product. - You can sometimes replace the use of one class with another by adding the following to your _config.php - file: Object::useCustomClass("OldObject","NewObject"); + `MyCustomCategory` might extend `ProductCategory`. Or `MyCustomProduct` might extend `Product`. + * You can also use the [Injector](https://docs.silverstripe.org/en/developer_guides/extending/injector/) to override/replace classes with your custom implementation. + +This should *always* be your preferred order of doing things: + + 1. Look if there's a config setting that already does what you need. + 2. Try to add your custom functionality with Extensions (`DataExtension` or `Extension`) and by implementing the hooks provided by the shop-module. + 3. Write a custom class and use the Injector to swap out the class you want to replace. - If these are bugfixes, or additional features that the core code would benefit from, please feel - free to [contribute back](02_Customisation/Contributing.md). +If these are bugfixes, or additional features that the core code would benefit from, please feel free to [contribute back](02_Customisation/Contributing.md). ## Theming / Templates There are a number of templates you can customise to create your desired look for the shop module. -The Order template has been modularised using SilverStripe's <% include TemplateName %> tag to provide +The Order template has been modularised using SilverStripe's `<% include TemplateName %>` tag to provide varying degrees of freedom to customise. The order template is used in both the Account page to display summaries of past orders, and it is also used with the email template. To make your customisations you need to create your own corresponding version of the diff --git a/docs/en/03_How_It_Works/Order_Modifiers.md b/docs/en/03_How_It_Works/Order_Modifiers.md index 896dfa2e4..a8bca90d8 100644 --- a/docs/en/03_How_It_Works/Order_Modifiers.md +++ b/docs/en/03_How_It_Works/Order_Modifiers.md @@ -39,9 +39,9 @@ Modifiers are introduced using the SilverStripe config system: ```yaml Order: - modifiers: - - SimpleShippingModifier - - FlatTaxModifier + modifiers: + - SimpleShippingModifier + - FlatTaxModifier ``` Note that you may need to clear the current order to see updates to modifiers. You can do this by visiting `mysite.com/shoppingcart/clear` diff --git a/docs/en/03_How_It_Works/Product_Variations.md b/docs/en/03_How_It_Works/Product_Variations.md index e24c77490..adb5edc8d 100644 --- a/docs/en/03_How_It_Works/Product_Variations.md +++ b/docs/en/03_How_It_Works/Product_Variations.md @@ -11,23 +11,17 @@ This is because not every website will need variations support, and thus it shou simple to disable / remove. * Product - * has_many ProductVariation - - * many_many AttributeValues + * many_many AttributeValues * many_many VariationAttributeTypes - - * has_many AttributeValues + * has_many AttributeValues ## Front-end Choosing a Variation You can either provide a list of possible variations to the visitor, or present a form for selecting the options they want. Each approach has pros and cons. -Listing all variations in a table is useful for presenting all possible variations, -particularly when there are some obsucre combinations. For example, only having two -options of: large red ball, or small green ball. Using a table becomes un practical -when the total number of variations for a particular product is high. +Listing all variations in a table is useful for presenting all possible variations,particularly when there are some obsucre combinations. For example, only having two options of: large red ball, or small green ball. Using a table becomes unpractical when the total number of variations for a particular product is high. Presenting options in a form is probably a more common approach. It keeps the presentation of options compact, and easy to comprehend. diff --git a/docs/en/03_How_It_Works/Release_Process.md b/docs/en/03_How_It_Works/Release_Process.md index 95856b753..12ebb6e0e 100644 --- a/docs/en/03_How_It_Works/Release_Process.md +++ b/docs/en/03_How_It_Works/Release_Process.md @@ -7,6 +7,7 @@ Complete a test order to see that everything is fine for the user. ## Generate Change Log Create change logs since last tag: + ```sh cd shop #New stuff @@ -16,6 +17,7 @@ git log --pretty=format:" * %s" --since="$(git show -s --format=%ad `git rev-lis #Bug fixes git log --pretty=format:" * %s" --since="$(git show -s --format=%ad `git rev-list --tags --max-count=1`)" --grep='^bug\|^fix' -i ``` + Copy output into change log. ## Versioning diff --git a/docs/en/03_How_It_Works/Submitting_Pull_Requests.md b/docs/en/03_How_It_Works/Submitting_Pull_Requests.md index f73ca0063..c09460343 100644 --- a/docs/en/03_How_It_Works/Submitting_Pull_Requests.md +++ b/docs/en/03_How_It_Works/Submitting_Pull_Requests.md @@ -5,11 +5,11 @@ Fork the project: Visit the [shop project page](https://github.com/burnbright/si Clone to your local machine, indie your silverstripe root directory. $ cd www/silverstripe - $ git clone git@github.com:jedateach/silverstripe-shop.git shop + $ git clone git@github.com:silvershop/silvershop-core.git silvershop Change folder - $ cd shop + $ cd silvershop Create a branch for your changes @@ -42,7 +42,7 @@ Done. The owner will receive your request, and merge in your code, or reject it, with some reason. -Here it is: https://github.com/burnbright/silverstripe-shop/pull/57 +Here it is: https://github.com/silvershop/silvershop-core/pull/57 ## Why make changes on a separate branch? diff --git a/docs_user/en/ProductVariations.md b/docs_user/en/ProductVariations.md index 1616f2ef6..7635a625d 100644 --- a/docs_user/en/ProductVariations.md +++ b/docs_user/en/ProductVariations.md @@ -1,16 +1,14 @@ Product Variations ================== -With the eCommerce module is possible to sell variants of the same product. Doing so keeps your -store looking tidy when users browse the site. Here we explain how you can manage these variations. +With the SilverShop module it is possible to sell variants of the same product. Doing so keeps your store looking tidy when users browse the site. Here we explain how you can manage these variations. -A product can have an unlimited number of variations. Each variation has a individual price and -product id associated with it. +A product can have an unlimited number of variations. Each variation has a individual price and product id associated with it. A few terms to understand: _Variation, Attribute Type, Attribute Value._ This table for a "Ball" product explains the terminology visually: -![Here is an example table to help demonstrate](\images\product-variation-table.jpg) +![Here is an example table to help demonstrate](images/product-variation-table.jpg) Each variation also has one or more attribute values of a specific type. The ball example above has a variation with the attribute value 'small' for the attribute type 'size'. @@ -19,48 +17,40 @@ The ball example above has a variation with the attribute value 'small' for the Creating Attribute Types and Values ----------------------------------- -Before you create a product variation, you should first create a some attributes types, with values -to choose from. +Before you create a product variation, you should first create some attribute types, with values to choose from. - 1. Create Attribute Types + 1. Create Attributes - * In the CMS, select the product you wish to edit. - * Find the 'Content > Variations' tab for your product. - * Click the "Add Product Attribute Type" link in the "Variation Attribute Types" table. + * In the CMS, navigate to "Catalog" and open the "Attribute" Tab + * Click the "Add Attribute" Button to add a new Attribute. * Give the new attribute type a name (eg: "Shoe Size") and label (eg: "Size"). The 'label' is used on the front-end of the website. - * Click 'Save' - - 2. Assign attribute types to the product - - * Check the newly created attribute type in the 'Variation Attribute Types' table. - * Save and publish the product page. - * Note that you should save the product after checking each attribute type. The attribute types may not be set otherwise. - - 3. Create Attribute Values - - * Click the product attribute type edit icon. - * Switch to the 'Values' tab. - * Fill out 'Value' and 'Sort' table as required. - - Note, you can also manage product attribute types and values in the "Products" section: - - * Switch to the 'Products' section of the SilverStripe CMS. - * Choose 'Product Attribute Type' from the 'Search for:' drop-down list on the left. - * Find and click on the attribute type you want to add values to. - * In the 'Values' tab, click the 'Add Product Attribute Value' link and add any number of values. You can set a sort value for each, if appropriate eg: "small" would need to come before "medium". + * Click "Create" (or "Save" if it's an existing Attribute) + + 2. Add Attribute Values + + * Open the Attribute you want to edit in the "Catalog" > "Attribute" Tab + * Click the "Add Value" Button to add a new Attribute Value + * Enter the Value (eg. "XL") + * Click "Create" (or "Save" if it's an existing Value) Creating a Product Variation ---------------------------- -Once you have set up some attribute types and values, and then assigned some attribute types to your product, -you can create variations for it. +Once you have set up some attribute types and values, you can create variations for Products. Firstly, navigate to the Product you want to add Variations to, either via "Pages" or "Catalog" + +1. Select the Attributes that will be part of the Variations - * Find the 'Variations' tab for the product. - * Click 'Add Product Variation' + * Find the "Variations" tab for the product. + * Add all the Attributes that will be part of the Variations in the "Attributes" select field. + * Save the Product + +2. Add Variations + + * Click the "Add Variation" Button * Fill out a product code, and price. - * Select attribute values from the attribute type drop-downs. - * Save + * Select the attribute values from the attribute type drop-downs. + * Click "Create" (or "Save" if it's an existing Variation) Creating and updating variations from a spreadsheet diff --git a/javascript/OrderActionsForm.js b/javascript/OrderActionsForm.js new file mode 100644 index 000000000..d506343d9 --- /dev/null +++ b/javascript/OrderActionsForm.js @@ -0,0 +1,30 @@ +;(function ($) { + $(function () { + // Helper JavaScript to toggle Credit-Card fields, depending on selected gateway + var handleGatewayChanged = function () { + // Get the currently selected gateway + var selected = $("#PaymentMethod input:checked").val(); + // Find credit-card input fields + var ccInput = $("#PaymentMethod").nextAll('.credit-card'); + if (ccInput && ccInput.length > 0) { + // Find gateway lookup data + var lookup = ccInput.find(".gateway-lookup").data("gateways"); + if (lookup && (selected in lookup)) { + // Show the Credit-Card fields if the gateway is in the lookup data + ccInput.show(); + // Hide all CC fields by default + ccInput.find(".field").hide(); + $(lookup[selected]).each(function (i, v) { + // only show the required fields + ccInput.find("[name=" + v + "]").parents(".field").show(); + }); + } else { + ccInput.hide(); + } + } + }; + + $("#PaymentMethod input").on("change", handleGatewayChanged); + handleGatewayChanged(); + }); +})(jQuery); diff --git a/lang/de.yml b/lang/de.yml index 4472a6a9d..96acde093 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -1,654 +1,494 @@ ---- de: - AccountNavigation: - AddressBook: Adressbuch - EditProfile: "Nutzerprofil bearbeiten" - LogOut: Abmelden + OrdersAdmin: + MENUTITLE: Bestellungen + ProductCatalogAdmin: + MENUTITLE: Katalog + ZoneAdmin: + MENUTITLE: Zonen + AbandonedCartReport: + Description: 'Übersicht der nicht bestellten Warenkörbe für bestimmte Zeitperioden. Resultate können nach Jahr, Monat oder Tag gruppiert werden.' + Title: 'Nicht bestellte Warenkörbe' + AccountPage: + AddressBook: 'Adressbuch' + DESCRIPTION: 'Hier kann der Kunde sein Konto bearbeiten und getätigte Bestellungen einsehen.' + DefaultTitle: Kundenkonto + EditProfile: 'Nutzerprofil bearbeiten' + LogOut: 'Abmelden' + Login: "Sie müssen sich einloggen um auf Ihr Konto zugreifen zu können. Falls Sie nicht registriert sind, können Sie erst nach Ihrer ersten Bestellung auf Ihr Konto zugreifen. Fall Sie bereits registriert sind, geben Sie folgend Ihre Daten ein." + LoginAgain: 'Sie sind abgemeldet. Wenn Sie sich wieder anmelden möchten, können Sie es weiter unten tun.' MemberEmail: E-Mail - MemberLastVisit: "Letzte Anmeldung" + MemberLastVisit: 'Letzte Anmeldung' MemberName: Name - MemberSince: "Kunde seit" - NumberOfOrders: "Anzahl der Bestellungen" - PastOrders: "Frühere Bestellungen" - Title: "Mein Kundenkonto" - AccountPage: - DESCRIPTION: "Kundenkonto-Seite" - LOGIN: "Sie müssen sich einloggen, um auf Ihr Konto zugreifen zu können. Wenn Sie nicht als Kunde registriert sind, können Sie sich hier nicht anmelden bis Sie Ihre erste Bestellung getätigt haben." - LOGINAGAIN: "Sie sind abgemeldet. Wen Sie sich wieder anmelden möchten, können Sie es weiter unten tun." - Message: "Sie müssen sich einloggen um auf Ihr Konto zugreifen zu können. Falls Sie nicht registriert sind, können Sie erst nach Ihrer ersten Bestellung auf Ihr Konto zugreifen. Fall Sie bereits registriert sind, geben Sie folgend Ihre Daten ein." - NOPAGE: "Keine Kundenkonto-Seite auf dieser Website - erstellen Sie bitte eine!" - NoPastOrders: "Keine früheren Bestellungen gefunden." - PLURALNAME: Kundenkontos - SINGULARNAME: Kundenkonto - Title: "Frühere Bestellungen" - NO_PAGE: "Es wurde keine Kundenkonto-Seite gefunden. Bitte erstellen Sie eine im CMS!" - AccountPage.ss: - COMPLETED: "Abgeschlossene Bestellungen" - HISTORY: "Ihr Bestellhistorie" - INCOMPLETE: "Offene Bestellungen" - NOCOMPLETED: "Es konnten keine abgeschlossenen Bestellungen gefunden werden." - NOINCOMPLETE: "Es konnten keine offenen Bestellungen gefunden werden." - ORDER: Bestellung - READMORE: "Zur Detail-Ansicht der Bestellung #%s" + MemberSince: 'Kunde seit' + NoPage: 'Es wurde keine Kundenkonto-Seite gefunden. Bitte erstellen Sie eine im CMS!' + NoPastOrders: 'Keine früheren Bestellungen gefunden.' + NumberOfOrders: 'Anzahl der Bestellungen' + PLURALNAME: 'Kundenkonto-Seiten' + PastOrders: 'Frühere Bestellungen' + SINGULARNAME: 'Kundenkonto-Seite' + Title: 'Mein Konto' AccountPage_AddressBook: - CreateNewTitle: "Neue Adresse eingeben" - NoAddress: "Keine Adresse gefunden." - Title: Standardadresse + CreateNewTitle: 'Neue Adresse erstellen' + NoAddress: 'Keine Adresse gefunden.' + Title: 'Standard Adressen' + DefaultShippingAddress: 'Standard-Lieferadresse' + DefaultBillingAddress: 'Standard-Rechnungsadresse' + MakeDefaultShipping: 'Als Standard-Lieferadresse' + MakeDefaultShippingTitle: 'Verwendet diese Adresse als Standard-Lieferadresse' + MakeDefaultBilling: 'Als Standard-Rechnungsadresse' + MakeDefaultBillingTitle: 'Verwendet diese Adresse als Standard-Rechnungsadresse' + DeleteAddress: 'Lösche diese Adresse' AccountPage_EditProfile: - Title: "Nutzerprofil bearbeiten" - AccountPage_order.ss: - ADDRESS: Adresse - AMOUNT: Menge - BACKTOCHECKOUT: "Klicken Sie hier um zur Kasse zu gelangen" - CITY: Stadt - COUNTRY: Land - DATE: Datum - DETAILS: Details - EMAILDETAILS: "Zur Bestätigung Ihrer Bestellung wurde eine Kopie an Ihre E-Mail Adresse geschickt." - NAME: Name - PAYMENTMETHOD: Bezahlmethode - PAYMENTSTATUS: "Status der Zahlung" - AddProductForm: - ADDTOCART: "In den Warenkorb" - Quantity: Menge + Title: 'Nutzerprofil bearbeiten' Address: - ADDRESS: Adresse - ADDRESS2HINT: "Gebäude, Wohnung, Etage, Zimmer,..." - ADDRESSHINT: "Straße und Nr." - ADDRESSLINE2: " " - BillingAddress: Rechnungsadresse - CITY: Stadt - CITYHINT: "" - COUNTRY: Land - PHONE: Telefonnummer + AddressHint: 'Straße und Hausnummer' + AddressLine2Hint: 'Gebäude, Wohnung, Etage, Postfach, etc.' + BillingAddress: 'Rechnungsadresse' + CityHint: 'Stadt, Bezirk, Landkreis' + CreateNewAddress: 'Neue Adresse erstellen' + ExistingBillingAddress: 'Bestehende Rechnungsadresse' + ExistingShippingAddress: 'Bestehende Lieferadresse' PLURALNAME: Adressen - POSTALCODE: Postleitzahl SINGULARNAME: Adresse - STATE: "Staat" - STATEHINT: "…oder Provinz, Gemeinde, Kanton, Insel" - SaveDefaults: "Standards speichern" - SaveNew: "Neue Adresse speichern" - ShippingAddress: Lieferadresse - db_Address: Adresse - db_AddressLine2: " " + SaveDefaults: 'Standards speichern' + SaveNew: 'Neue Adresse speichern' + ShippingAddress: 'Lieferadresse' + StateHint: '…oder Provinz, Gemeinde, Kanton, Insel' + db_Address: Adresszeile 1 + db_AddressLine2: 'Adresszeile 2 (optional)' db_City: Stadt db_Company: Firma db_Country: Land - db_FirstName: Vorname - db_Latitude: Breitengrad - db_Longitude: Längengrad - db_Phone: Telefonnummer - db_PostalCode: Postleitzahl - db_State: "Staat" + db_FirstName: 'Vorname' + db_Phone: 'Telefonnummer' + db_PostalCode: 'Postleitzahl' + db_State: Staat db_Surname: Nachname has_one_Member: Kunde - AddressBookCheckoutComponent: - CREATENEWADDRESS: "Adresse ändern" - EXISTING_BILLING_ADDRESS: "Vorhandene Rechnungsadresse" - EXISTING_SHIPPING_ADDRESS: "Vorhandene Lieferadresse" - EXISTING_ADDRESS: "Vorhandene Adresse" - Cart.ss: - ADDONE: "Eines oder mehr von \"%s\" zum Warenkorb hinzufügen" - CheckoutClick: "Hier klicken um zur Kasse zu gelangen" - CheckoutGoTo: "Zur Kasse" - HEADLINE: "Mein Warenkorb" - NOITEMS: "In Ihrem Warenkorb befinden sich zur Zeit keine Artikel" - PRICE: Preis - PRODUCT: Produkt - QUANTITY: Menge - READMORE: "Erfahren Sie hier mehr über \"%s\"" - REMOVE: "\"%s\" aus dem Warenkorb entfernen" - REMOVEALL: "Alle \"%s\" aus dem Warenkorb entfernen" - REMOVEONE: "Entfernen Sie eines von \"%s\" aus Ihrem Warenkorb" - SUBTOTAL: Zwischensumme - TABLESUMMARY: "Im Warenkorb befindliche Produkte" - TOTAL: Gesamt - TOTALPRICE: Gesamtpreis - UNITPRICE: Einzelpreis + CartForm: + REMOVED_ITEMS: '{count} Artikel entfernt' + UPDATED_ITEMS: '{count} Artikel aktualisiert.' + UpdateCart: 'Warenkorb aktualisieren' CartPage: - DESCRIPTION: "Warenkorb-Seite" - PLURALNAME: Warenkorb - SINGULARNAME: Warenkorb - TITLE: Warenkorb - has_one_CheckoutPage: Kassenseite - has_one_ContinuePage: Weiterleitungsseite - CartPage.ss: - CARTEMPTY: "Ihr Warenkorb ist leer." - CONTINUE: "Den Einkauf fortsetzen" - PROCEEDTOCHECKOUT: "Zur Kasse" + DESCRIPTION: 'Seite auf der der Kunde seinen Warenkorb ansehen und modifizieren kann.' + DefaultTitle: 'Warenkorb' + PLURALNAME: 'Warenkorb-Seiten' + SINGULARNAME: 'Warenkorb-Seite' + has_one_CheckoutPage: 'Kassenseite' + has_one_ContinuePage: 'Weiterleitungsseite' Checkout: - IDFIELDNOTFOUND: "Pflichtfeld nicht gefunden: %s" - MEMBEREXISTS: "Es existiert bereits ein Kundenkonto mit der %s %s" - MEMBERSHIPSNOTALLOWED: "Das Erstellen von neuen Kundenkonten ist nicht erlaubt, bitte kontaktieren Sie uns telefonisch oder per E-Mail" - NOPAYMENTMETHOD: "Die Zahlungsart existiert nicht" - PASSWORDREQUIRED: "Passwort ist erforderlich" + IdFieldNotFound: 'Pflichtfeld nicht gefunden: {IdentifierField}' + MemberExists: 'Es existiert bereits ein Kundenkonto mit der {Field} {Identifier}' + MembershipIsNotAllowed: 'Das Erstellen von neuen Kundenkonten ist nicht erlaubt, bitte kontaktieren Sie uns telefonisch oder per E-Mail.' + NoPaymentMethod: 'Die Zahlungsart existiert nicht.' + PasswordRequired: 'Passwort ist erforderlich' + TermsAndConditionsLink: 'Ich habe die {TermsPageTitle} gelesen und akzeptiert.' CheckoutComponentValidator: - INVALIDMESSAGE: "Es gibt Probleme mit den von Ihnen eingegebenen Daten. Siehe unten:" + InvalidDataMessage: 'Es gibt Probleme mit den von Ihnen eingegebenen Daten. Siehe unten:' CheckoutField: - ACCOUNTINFO: "Bitte wählen Sie ein Passwort, damit Sie sich zukünftig im Kundenkonto einloggen können." - EMAIL: E-Mail - FIRSTNAME: Vorname - MEMBERINFO: "Wenn Sie schon Kunde bei uns sind, klicken Sie bitte hier um sich anzumelden." - MEMBERSHIPDETAILS: Kundeninformationen - MUSTAGREETOTERMS: "Bitte bestätigen Sie, dass sie die AGB gelesen haben" - NOTES: Bemerkungen - PASSWORD: Passwort - SURNAME: Nachname - TERMSANDCONDITIONS: "Ich habe die Allgemeinen Geschäftsbedingungen gelesen und akzeptiert." - PAYMENT_TYPE: Zahlungsart - CheckoutFields: - PAYMENTTYPE: Zahlungsart - CheckoutForm: - PROCEED: "Weiter zur Zahlung" + AccountInfo: 'Bitte wählen Sie ein Passwort, damit Sie sich zukünftig im Kundenkonto einloggen können.' + MemberLoginInfo: 'Wenn Sie schon Kunde bei uns sind, klicken Sie bitte hier um sich anzumelden.' + MembershipDetails: 'Kundeninformationen' + MustAgreeToTerms: 'Bitte bestätigen Sie, dass sie die AGB gelesen haben.' + Password: Passwort + PaymentType: 'Zahlungsart' CheckoutPage: - DESCRIPTION: "Kassenseite" - NOPAGE: "Auf dieser Site existiert keine Kassenseite - bitte erstellen Sie eine neue Seite!" - PLURALNAME: Kassenseiten - PaymentErrorMessage: "Fehler von Payment-Gateway empfangen:" - SINGULARNAME: Kassenseite - TITLE: Kasse - db_PurchaseComplete: "Bestellung abgeschlossen" - PURCHASE_COMPLETE_DESCRIPTION: "Diese Meldung wird dem Kunden im Bestätigungs-E-Mail gesendet, nachdem der Bestellvorgang abgeschlossen wurde." - SUBMIT_PAYMENT: "Bezahlen" - CheckoutPage.ss: - CARTEMPTY: "Ihr Warenkorb ist leer." - CHECKOUT: Kasse - ORDERSTATUS: Bestellstatus - PROCESS: Prozess - ChequePayment: - MESSAGE: "Bezahlung per Scheck (Vorkasse). Bitte beachten: Der Versand der Produkte erfolgt erst nach Zahlungseingang." + DESCRIPTION: 'Seite auf der der Bestellprozess abgewickelt wird.' + DefaultTitle: Kasse + PLURALNAME: 'Kassenseiten' + PaymentErrorMessage: 'Fehler von Payment-Gateway empfangen:' + ProceedToPayment: 'Weiter zur Bezahlseite' + PurchaseCompleteDescription: 'Diese Meldung wird dem Kunden im Bestätigungs-E-Mail gesendet, nachdem der Bestellvorgang abgeschlossen wurde.' + SINGULARNAME: 'Kassenseite' + SubmitPayment: 'Bezahlen' + db_PurchaseComplete: 'Bestellung abgeschlossen' + CheckoutStep: + Continue: Weiter + Shipping: Lieferung + Summary: Zusammenfassung + CheckoutStep_Address: + BillTo: 'Rechnung an:' + EnterShippingAddress: 'Bitte geben Sie Ihre gewünschte Lieferadresse ein.' + SeperateBilling: 'Abweichende Rechnungsadresse angeben' + ShipTo: 'Lieferung an:' + SupplyContactInformation: 'Bitte geben Sie Ihre Kontaktdaten an' + CheckoutStep_Membership: + ContinueAsGuest: 'Weiter ohne Kundenkonto' + CreateAccount: 'Konto erstellen' + CreateNewAccount: 'Neues Konto erstellen' CreateAddressForm: - SAVED: "Ihre Adresse wurde gespeichert" + AddressSaved: 'Ihre Adresse wurde gespeichert' + CustomerReport: + Description: 'Kunden-Report' + Title: Kunden FlatTaxModifier: PLURALNAME: Steuern - SINGULARNAME: MwSt. + SINGULARNAME: Steuer FreeShippingModifier: - FREE: Kostenlos - PLURALNAME: Modifikatoren - SINGULARNAME: Versand - General: - DATEFORMATNICE: "%d.%m.%Y" - DATETIMEFORMATNICE: "%d.%m.%Y %H:%M" - DATETIMEFORMATNICE24: "d.m.Y H:i" + Free: Kostenlos + PLURALNAME: 'Versand Kostenlos' + SINGULARNAME: Versandkosten GlobalTaxModifier: - INCLUDED: " (im Preis enthalten)" + Included: '(im Preis enthalten)' PLURALNAME: Steuern SINGULARNAME: MwSt. db_Country: Land MemberForm: - DETAILSSAVED: "Ihre Angaben wurden gespeichert" - LOGGEDIN: "Sie sind angemeldet als " - SAVE: "Änderungen speichern" - SAVEANDPROCEED: "Speichern und weiter zur Kasse" - ORDER: - INVOICE: Rechnung + DetailsSaved: 'Ihre Angaben wurden gespeichert' + Save: 'Änderungen speichern' + SaveAndProceed: 'Speichern und weiter zur Kasse' + OnsitePaymentCheckoutComponent: + InvalidCreditCard: 'Kreditkarte ist ungültig' Order: - CANCELSUBJECT: "Die Bestellung Nummer #%d wurde vom Kunden storniert." - INCOMPLETE: "Bestellung unvollständig" + BillTo: 'Rechnung an' + Date: Datum + DateFrom: 'Datum von' + DateTo: 'Datum bis' + GrandTotal: 'Gesamtbetrag' + Invoice: Rechnung + OrderHeadline: 'Bestellung Nr. {OrderNo} vom {OrderDate}' + Outstanding: Ausstehend + OutstandingWithAmount: 'Ausstehend: {Amount}' PLURALNAME: Bestellungen - PRINT: Drucken + Print: Drucken + Quantity: Menge SINGULARNAME: Bestellung - SUCCESSFULL: "Bestellung Erfolgreich" - db_AnalyticsSubmitted: "Analytics eingereicht" - db_Dispatched: "Wird ausgelöst" - db_Email: Email - db_FirstName: Vorname - db_Gesamt: Zwischensumme - db_IPAddress: IP-Adresse + STATUS_ADMINCANCELLED: 'Storniert durch Administrator' + STATUS_CART: Warenkorb + STATUS_COMPLETE: Abgeschlossen + STATUS_MEMBERCANCELLED: 'Storniert durch Kunde' + STATUS_PAID: Bezahlt + STATUS_PROCESSING: In Bearbeitung + STATUS_SENT: Versendet + STATUS_UNPAID: Unbezahlt + ShipTo: 'Lieferung an' + SubTotal: Zwischensumme + Total: Gesamt + TotalOutstanding: 'Gesamt ausstehend' + TotalPrice: 'Gesamtpreis' + TotalPriceWithCurrency: 'Gesamtpreis ({Currency})' + UnitPrice: 'Stückpreis' + db_Dispatched: Ausgelöst + db_Email: E-Mail + db_FirstName: 'Vorname' + db_IPAddress: 'IP-Adresse' + db_Notes: Nachricht db_Paid: Bezahlt - db_Placed: Platziert - db_Printed: Gedruckt - db_ReceiptSent: "Bestätigung gesendet" + db_Placed: Eingegangen + db_Printed: Ausgedruckt + db_ReceiptSent: 'Bestätigung gesendet' db_Reference: Bestellnummer - db_SeparateBillingAddress: "Abweichende Lieferadresse" - db_ShippingGesamt: "Gesamtkosten für den Versand" + db_SeparateBillingAddress: 'Abweichende Lieferadresse' db_Status: Status db_Surname: Nachname + db_Total: Gesamt has_many_Items: Artikel - has_many_Modifikatoren: Modifikatoren - has_many_OrderStatusLogs: "Bestell-Status Logs" + has_many_Modifiers: Modifikatoren + has_many_OrderStatusLogs: 'Bestell-Status Logs' has_many_Payments: Zahlungen - has_one_BillingAddress: Rechnungsadresse + has_one_BillingAddress: 'Rechnungsadresse' has_one_Member: Kunde - has_one_ShippingAddress: Lieferadresse - has_one_ShippingZahlungsart: Versandart - Order.ss: - ORDERNOTES: Bemerkungen - TOTALOUTSTANDING: "Gesamt ausstehend" + has_one_ShippingAddress: 'Lieferadresse' OrderActionsForm: - CANCELORDER: "Diese Bestellung stornieren" - MAKEPAYMENT: Zahlen - OUTSTANDING: "Ausstehend: %s" - PAYMENTMETHOD: Zahlungsart - PAYORDER: "Restbetrag bezahlen" - MANUAL_NOT_ALLOWED: "Bezahlung gegen Rechnung nicht erlaubt." - OrderAdmin_Addresses.ss: - ADDRESS: Adresse - BILLTO: "Rechnung an" - SHIPTO: "Liefern an" - OrderAdmin_Content.ss: - ITEMS: Artikel - PRODUCT: Produkt - QUANTITY: Menge - TOTALPRICE: Gesamtpreis - UNITPRICE: Stückpreis - OrderAdmin_Content_ItemLine.ss: - READMORE: "Zeige \"%s\"" - OrderAdmin_Content_SubGesamts.ss: - SUBTOTAL: Zwischensumme - TOTAL: Gesamt - TOTALOUTSTANDING: Ausstehend - OrderAdmin_Customer.ss: - CUSTOMER: Kunde - NOTES: Bemerkungen - OrderAdmin_Printable.ss: - ORDER: Bestellung + CancelOrder: 'Diese Bestellung stornieren' + MakePayment: 'Zahlung tätigen' + ManualNotAllowed: 'Bezahlung gegen Rechnung nicht erlaubt.' + PayOrder: 'Restbetrag bezahlen' + PaymentMethod: 'Zahlungsart' + OrderAdmin: + ReceiptTitle: '{SiteTitle} Bestellung Nr. {OrderNo}' OrderAttribute: PLURALNAME: Attribute SINGULARNAME: Attribut - db_CalculatedGesamt: Gesamtbetrag + db_CalculatedTotal: 'Gesamtbetrag' has_one_Order: Bestellung OrderForm: - COULDNOTPROCESSPAYMENT: "Die Zahlung konnte nicht verarbeitet werden." - LogIn: Anmelden - ORDERCANCELLED: "Bestellung erfolgreich storniert" - OrderHistory: - OrderDatum: Datum - OrderGesamt: Zwischensumme - OrderItems: Produkte - OrderReference: Bestellnummer - OrderStatus: Status - ViewOrder: anzeigen + CouldNotProcessPayment: 'Die Zahlung konnte nicht verarbeitet werden.' + OrderCancelled: 'Bestellung erfolgreich storniert' OrderItem: PLURALNAME: Produkte SINGULARNAME: Produkt db_Quantity: Menge - db_UnitPrice: Stückpreis + db_UnitPrice: 'Stückpreis' OrderModifier: PLURALNAME: Modifikatoren SINGULARNAME: Modifikator - db_Betrag: Betrag - db_Sort: Sortieren + db_Amount: Betrag + db_Sort: Sortierfolge db_Type: Art - OrderNotifier: - ADMINNOTIFICATIONSUBJECT: "Bestellbenachrichtigung #%d" - CONFIRMATIONSUBJECT: "Bestellbestätigung #%d" - RECEIPTSUBJECT: "Bestellungseingang #%d" OrderProcessor: - NULL: "Eine neue Bestellung wurde noch nicht begonnen" - NOITEMS: "Die Bestellung beinhaltet keine Artikel." - NOTCART: "Die Bestellung beinhaltet keinen Warenkorb." + NoItems: 'Die Bestellung beinhaltet keine Artikel.' + NoOrder: 'Die Bestellung existiert nicht.' + NoOrderStarted: 'Es wurde noch keine neue Bestellung gestartet.' + NotCart: 'Die Bestellung beinhaltet keinen Warenkorb.' OrderStatusLog: - PLURALNAME: "Bestell Status Log Einträge" - SINGULARNAME: "Bestell Log Einträge" - db_DispatchTicket: "Versand Ticket" - db_DispatchedBy: "Versand von" - db_DispatchedOn: "Versand mit" - db_PaymentCode: "Zahlungs ID" - db_PaymentOK: "Zahlung OK" - db_SentToCustomer: "An Kunden verschickt" + PLURALNAME: 'Bestellstatus Log-Einträge' + SINGULARNAME: 'Bestellstatus Log-Eintrag' + db_DispatchTicket: 'Versand Ticket' + db_DispatchedBy: 'Versendet durch' + db_DispatchedOn: 'Versendet am' + db_Note: Notiz + db_PaymentCode: 'Zahlungs-Code' + db_PaymentOK: 'Zahlung OK' + db_SentToCustomer: 'An Kunden verschickt' db_Title: Titel has_one_Author: Autor has_one_Order: Bestellung - Order_Address.ss: - BILLTO: "Rechnung an" - SHIPTO: "Lieferung an" - Order_AdminNotificationEmail.ss: - TITLE: Shop-Eingang - Order_ConfirmationEmail.ss: - TITLE: Bestellbestätigung - Order_Content.ss: - NOITEMS: "Ihre Bestellung weist keine Artikel auf" - PRICE: Preis - PRODUCT: Produkt - QUANTITY: Menge - READMORE: "Wenn Sie mehr über \"%s\" erfahren möchten, klicken Sie hier" - SUBTOTAL: Zwischensumme - TOTAL: Gesamt - TOTALOUTSTANDING: "Gesamt ausstehend" - TOTALPRICE: Gesamtpreis - UNITPRICE: Stückpreis - Order_Content_ItemLine.ss: - READMORE: "Zeige \"%s\"" - Order_Content_SubGesamts.ss: - SUBTOTAL: Zwischensumme - TOTAL: Gesamt - Order_ItemLine.ss: - READMORE: "Zeige \"%s\"" - Order_Member.ss: - ADDRESS: Adresse - CITY: Stadt - COUNTRY: Land - EMAIL: E-Mail - MOBILE: Handy - NAME: Name - PHONE: Telefon - Order_Payments.ss: - AMOUNT: Betrag - DATE: Datum - PAYMENTMETHOD: Zahlungsart - PAYMENTNOTE: Bemerkungen - PAYMENTSTATUS: Zahlungsstatus - Order_ReceiptEmail.ss: - HEADLINE: Auftragsbestätigung - TITLE: Shop-Eingang - Order_StatusEmail.ss: - HEADLINE: "Shop Status geändert" - STATUSCHANGE: "Status geändert zu \"%s\" für Bestellung Nummer #" - TITLE: "Shop Status geändert" - OrdersAdmin: - MENUTITLE: Bestellungen - Payment: ~ + Payment: + Note: Notiz + PaymentsHeadline: Zahlung(en) + PaymentCheckoutComponent: + NoPaymentMethod: 'Es wurde keine Zahlungsmethode angegeben' + UnsupportedGateway: 'Zahlungsmethode wird nicht unterstützt' PaymentProcessor: - INVALID_GATEWAY: "`{gateway}` ist kein gültiges Bezahl-Gateway." - CANTPAY: "Diese Bestellung kann nicht bezahlt werden." + CantPay: "Diese Bestellung kann nicht bezahlt werden." + InvalidGateway: "'{gateway}' ist keine gültige Bezahlmethode." PickupShippingModifier: PLURALNAME: Modifikatoren - SINGULARNAME: Selbstabholung + SINGULARNAME: 'Selbstabholung' + PriceTag: + SAVE: Gespart Product: - ADDITIONALCATEGORIES: "Zusätzliche Produktkategorien" - ALLOWPURCHASE: "Zulassen, dass dieses Produkt bestellt werden darf" - CATEGORY: Category - CATEGORYDESCRIPTION: "Dies ist die übergeordnete Seite oder Standardkategorie." - CODE: Artikelnummer - CODE_SHORT: "Art. Nr." - COSTPRICE: Kostenpreis - COSTPRICEDESC: "Großhandelspreis ohne Preisaufschlag." - DEPTH: "Tiefe. (%s)" - DESCRIPTION: "Generic content page" - FEATURED: "Hervorgehobenes Produkt" - HEIGHT: "Height (%s)" - IMAGE: Produktabbildung - MODEL: "Die wichtigsten Artikelmerkmale" - PAGETITLE: Produktname + AddToCart: 'In den Warenkorb' + AddToCartTitle: '"{Title}" zum Warenkorb hinzufügen' + AdditionalCategories: 'Zusätzliche Produktkategorien' + AllowPurchase: 'Zulassen, dass dieses Produkt bestellt werden darf' + Category: Kategorie + CategoryDescription: 'Dies ist die übergeordnete Seite oder Standardkategorie.' + Code: 'Artikelnummer' + CostPriceDescription: 'Großhandelspreis ohne Preisaufschlag.' + DESCRIPTION: 'Produkte-Seite im Shop' + Featured: 'Hervorgehobenes Produkt' + Image: 'Produktabbildung' + ImageAltText: 'Bild zu {Title}' + InternalItemID: 'Artikelnummer' + Model: Wichtigste Artikelmerkmale + NoImage: 'Kein Bild vorhanden' PLURALNAME: Produkte - PRICE: Price - PRICEDESC: Basisverkaufspreis. + PageTitle: 'Produktname' + Price: Preis + PriceDesc: 'Basisverkaufspreis.' + ProductCodeShort: Art. Nr. SINGULARNAME: Produkt - WEIGHT: "Gewicht (%s)" - WIDTH: "Breite (%s)" - NO_IMAGE: "kein Bild" - db_AllowPurchase: "Kaufen erlauben" + Size: Größe + View: 'Produkt ansehen' + db_AllowPurchase: 'Kaufen erlauben' db_BasePrice: Basispreis - db_CostPrice: Kostenpreis + db_CostPrice: 'Kostenpreis' + db_Depth: Tiefe + db_Featured: Hervorgehoben db_Height: Höhe - db_Hervorgehoben: Hervorgehoben - db_InternalItemID: "Interne Produkt ID" + db_InternalItemID: 'Artikelnummer/SKU' + db_Model: Wichtigste Artikelmerkmale db_Popularity: Popularität - db_Tiefe.: Tiefe. db_Weight: Gewicht db_Width: Breite has_many_Variations: Variationen has_one_Image: Bild - many_many_ProductCategories: Produktkategorien - many_many_VariationAttributeTypes: Variationsattributtypen - Product.ss: - ADD: "\"%s\" zum Warenkorb hinzufügen" - ADDLINK: "Diesen Artikel zum Warenkorb hinzufügen" - ADDONE: "\"%s\" zum Warenkorb hinzufügen" - AUTHOR: Autor - CODE: Artikelnummer - FEATURED: "Wir empfehlen diesen Artikel." - GOTOCHECKOUT: "Jetzt zur Kasse gehen" - GOTOCHECKOUTLINK: "» Zur Kasse" - IMAGE: "%s Bild" - ItemID: "Artikel Nr." - MODEL: "Die wichtigsten Artikelmerkmale" - NOIMAGE: "Keine Produktabbildung vorhanden für \"%s\"" - QUANTITYCART: "Menge im Warenkorb" - REMOVE: "\"%s\" vom Warenkorb entfernen" - REMOVEALL: "\"%s\" vom Warenkorb entfernen" - REMOVELINK: "» Aus dem Warenkorb entfernen" - SIZE: Größe + many_many_ProductCategories: 'Produktkategorien' + many_many_VariationAttributeTypes: 'Variationsattributtypen' ProductAttributeType: - PLURALNAME: Eigenschaften - SINGULARNAME: Eigenschaft - SAVE_FIRST_MESSAGE: "Werte können nach erstmaligem Speichern erfasst werden." + PLURALNAME: Attribute + SINGULARNAME: Attribut + SaveFirstInfo: 'Werte können nach erstmaligem Speichern erfasst werden.' belongs_many_many_Product: Produkt - db_Label: Label + db_Label: Bezeichnung db_Name: Name - has_many_Values: Daten + has_many_Values: Werte ProductAttributeValue: - PLURALNAME: Daten - SINGULARNAME: Daten - belongs_many_many_ProductVariation: "Produkt Variationen" - db_Sort: Sortieren - db_Value: Daten + PLURALNAME: Attribut-Werte + SINGULARNAME: Attribut-Wert + belongs_many_many_ProductVariation: 'Produkt Variationen' + db_Sort: Sortierfolge + db_Value: Attribut-Wert has_one_Type: Art - ProductCatalogAdmin: - MENUTITLE: Katalog ProductCategory: - DESCRIPTION: "Generic content page" + DESCRIPTION: 'Produkte-Kategorie im Shop' PLURALNAME: Kategorien SINGULARNAME: Kategorie belongs_many_many_Products: Produkte - ProductCategory.ss: - FEATURED: "Empfohlene Artikel" - OTHER: "Andere Produkte" - ProductCategoryItem.ss: - ADD: "\"%s\" zum Warenkorb hinzufügen" - ADDLINK: "Diesen Artikel zum Warenkorb hinzufügen" - ADDONE: "\"%s\" zum Warenkorb hinzufügen" - AUTHOR: Autor - GOTOCHECKOUT: "Zur Kasse" - GOTOCHECKOUTLINK: "» Zur Kasse" - IMAGE: "%s Bild" - NOIMAGE: "Keine Produktabbildung vorhanden für \"%s\"" - QUANTITYCART: "Menge im Warenkorb" - READMORE: "Erfahren Sie hier mehr über \"%s\"" - READMORECONTENT: "mehr »" - REMOVE: "\"%s\" vom Warenkorb entfernen." - REMOVEALL: "1 Einheit von \"%s\" aus dem Warenkorb entferne" - REMOVELINK: "» Aus dem Warenkorb entfernen" + Alphabetical: Alphabetisch + Price: Preis ProductGroup: - NEXT: Nächste - PAGE: Seite - PREVIOUS: Vorherige - VIEW_PREVIOUS: "Zur vorherigen Seite" - VIEW_NEXT: "Zur nächsten Seite" - VIEW_PAGE: "Zeige Seite {pageNum}" - ProductGroupItem.ss: - ADD: "\"%s\" zum Warenkorb hinzufügen" - ADDLINK: "In den Warenkorb" - IMAGE: "%s Bild" - MODEL: "Die wichtigsten Artikelmerkmale" - READMORE: "Erfahren Sie hier mehr über \"%s\"" - VIEW: "Produkt öffnen" - ProductMenu.ss: - GOTOPAGE: "Gehe zur %s Seite" + Next: Nächste + Page: Seite + Previous: Vorherige + ViewNext: 'Zur nächsten Seite' + ViewPage: 'Zeige Seite {pageNum}' + ViewPrevious: 'Zur vorherigen Seite' + ProductMenu: + GotoPageTitle: 'Gehe zu: {Title}' + ProductReport: + Description: "Gibt Aufschluss über Verkaufszahlen der einzelnen Produkte. Hilft herauszufinden, welche Produkte sich gut/schlecht verkaufen." + Title: Produkte-Report ProductVariation: + MustSaveFirstMessage: 'Attribute können erst nach dem ersten Speichern ausgewählt werden (gesetzt der Fall, sie existieren).' + NoAttributeValuesMessage: 'Es gibt keine Werte für {attribute}. Diese können im ''Katalog'' erstellt werden.' PLURALNAME: Variationen SINGULARNAME: Variation - db_InternalItemID: "Interne Artikel ID" + db_InternalItemID: 'Artikelnummer' db_Price: Preis db_Version: Version has_one_Image: Bild has_one_Product: Produkt - many_many_AttributeValues: Eigenschaften - NO_ATTRIBUTE_VALUES_MESSAGE: "Es gibt keine Werte für {attribute}. Diese können im 'Katalog' erstellt werden." - SAVE_FIRST_MESSAGE: "Attribute können erst nach dem ersten Speichern ausgewählt werden (gesetzt der Fall, sie existieren)." - ProductVariationsExtension: - ATTRIBUTES: "Attribute" - ATTRIBUTES_DESCRIPTION: "Diese Felder definieren die Attribute welche die Variationen ausmachen. Sobald sie ausgewählt wurden, können sie in jeder Variation erfasst werden." - VARIATIONS: "Variationen" - VARIATIONS_INSTRUCTIONS: "Da mehrere Variationen vorhanden sind, muss der Preis bei den Variationen erfasst werden." + many_many_AttributeValues: 'Eigenschaften' ProductVariation_OrderItem: PLURALNAME: Artikel - SINGULARNAME: Artikel - db_ProductVariationVersion: "Produkt Variations Version" - has_one_ProductVariation: "Produkt Variation" + SINGULARNAME: Produkt + db_ProductVariationVersion: 'Variante/Version' + has_one_ProductVariation: 'Produkt Variationen' + ProductVariationsExtension: + Attributes: Attribute + AttributesDescription: 'Diese Felder definieren die Attribute welche die Variationen ausmachen. Sobald sie ausgewählt wurden, können sie in jeder Variation erfasst werden.' + Variations: Variationen + VariationsInfo: 'Da mehrere Variationen vorhanden sind, muss der Preis bei den Variationen erfasst werden.' Product_OrderItem: PLURALNAME: Artikel - SINGULARNAME: Artikel - db_ProductVersion: "Produkt Version" + SINGULARNAME: Produkt + db_ProductVersion: 'Produkt Version' has_one_Product: Produkt RegionRestriction: - PLURALNAME: "Regionale Restriktionen" - SINGULARNAME: "Regionale Restriktion" + PLURALNAME: 'Regionale Restriktionen' + SINGULARNAME: 'Regionale Restriktion' db_City: Stadt db_Country: Land - db_PostalCode: Postleitzahl + db_PostalCode: 'Postleitzahl' db_State: Staat SetLocationForm: - CHOOSECOUNTRY: "Land auswählen..." - COUNTRY: Land + ChooseCountry: 'Land auswählen…' + Country: Land ShippingModifier: PLURALNAME: Modifikatoren - SINGULARNAME: Versandkosten + SINGULARNAME: Lieferung Shop: - DEVTOOLSTITLE: Shop-Entwicklungstools + Change: Ändern + Customer: Kunde + Date: Datum + DateFormatNice: '%e. %B %G' + DateTimeFormatNice: '%e. %B %G %H:%M' + DateTimeFormatNice24: 'd.m.Y H:i' + DevToolsTitle: 'Shop Entwickler Tools' + Edit: Bearbeiten + Quantity: Menge + ReadMoreTitle: 'Für weitere Informationen zu "{Title}", hier klicken' + Remove: Entfernen + View: Ansehen ShopConfig: - CUSTOMERGROUP: "Gruppe, um neue Kunden zu ergänzen" - DEFAULTIMAGE: "Standard Produktbild" - TERMSPAGE: "Allgemeine Geschäftsbedingungen" - ALLOWED_COUNTRIES: "Länder für Bestellungen und Lieferungen" - ALLOWED_COUNTRIES_TAB_TITLE: "Erlaubte Länder" + AllowedCountries: 'Länder für Bestellungen und Lieferungen' + AllowedCountriesTabTitle: 'Erlaubte Länder' + CustomerGroup: 'Gruppe, zu der neue Kunden hinzugefügt werden' + DefaultImage: 'Standard Produktbild' + TermsPage: 'Allgemeine Geschäftsbedingungen' ShopCurrency: - FREE: "FREI" + Free: 'Kostenlos' ShopDashboard: - RECENTORDERS: "Letzte Bestellungen" - RECENTORDERSDESCRIPTION: "Letzte Bestellungen anzeigen" - RecentAccounts: "Neue Kundenkonten" - RecentAccountsDescription: "Die letzten Anmeldungen in Kundenkonten anzeigen" - RecentOrdersChart: "Bestellhistorie sortieren" - RecentOrdersChartDescription: "Zeigt die letzten Bestellungen in einem Diagramm" - NUMBER_OF_ACCOUNTS: "Anzahl anzuzeigender Benutzer" - NUMBER_OF_DAYS: "Anzahl anzuzeigender Tage" - NUMBER_OF_ORDERS: "Anzahl anzuzeigender Bestellungen" - # These should probably be moved into a more generic category, since they can be used in a lot of places - EDIT: "Bearbeiten" - CUSTOMER: "Kunde" - DATE: "Datum" - ShopDatabaseAdmin.ss: - BUILDTASKS: "Aufgaben anlegen" - CARTCLEANUP: "Alte Warenkörbe bereinigen" - CARTCLEANUPDESC: "Abgebrochene Warenkörbe entfernen" - CARTTASKS: "Warenkorbaufgaben anzeigen" - RECALCULATEORDERS: "Alle Bestellungen rekalkulieren" - RECALCULATEORDERSDESC: "Neu berechnen aller Bestellwerte. Warnung: Dies wird alle vorhergehenden Werte überschreiben." - RUNALLTESTS: "Alle Shop-Tests starten" - UNITTESTS: "Test der Einheit" - ShopDevelopmentAdmin.ss: - BUILDTASKS: "Aufgaben anlegen" - CARTCLEANUP: "Alte Warenkörbe bereinigen" - CARTCLEANUPDESC: "Abgebrochene Warenkörbe entfernen." - CARTTASKS: "Warenkorbaufgaben anzeigen" - CLEARCART: "Den aktuellen Warenkorb löschen" - DEBUGCART: "Den Warenkorb zurücksetzen" - DELETEORDERS: "Alle Bestellungen löschen" - DELETEORDERSDESC: "Alle Bestellungen, Modifikatoren und Zahlungen aus der Datenbank löschen." - DELETEPRODUCTS: "Delete All Products" - DELETEPRODUCTSDESC: "Alle Produkte aus der Datenbank löschen." - POPULATECART: "Befülle den Warenkorb mit einigen erhältlichen Produkten" - POPULATESHOP: "Befülle den Shop" - POPULATESHOPDESC: "Befülle den Shop mit Dummy-Produkten, Kategorien und erstelle andere notwendige Seiten." - RECALCULATEORDERS: "Alle Bestellungen rekalkulieren" - RECALCULATEORDERSDESC: "Neu berechnen aller Bestellwerte. Warnung: Dies wird alle vorhergehenden Werte überschreiben." - RUNALLTESTS: "Alle Shop-Tests starten" - UNITTESTS: "Test der Einheit" - ShopQuantityField.ss: - ADDONE: "\"%s\" zum Warenkorb hinzufügen" - REMOVEONE: "\"%s\" aus Warenkorb entferrnen" + NumberOfAccounts: 'Anzahl anzuzeigender Benutzer' + NumberOfDays: 'Anzahl anzuzeigender Tage' + NumberOfOrders: 'Anzahl anzuzeigender Bestellungen' + RecentAccounts: 'Neue Kundenkonten' + RecentAccountsDescription: 'Zeigt die zuletzt erstellten Kundenkonten' + RecentOrders: 'Letzte Bestellungen' + RecentOrdersChart: 'Diagramm der letzten Bestellungen' + RecentOrdersChartDescription: 'Zeigt die letzten Bestellungen in einem Diagramm' + RecentOrdersDescription: 'Zeigt die letzten Bestellungen' + ShopDevelopmentAdmin: + BuildTasks: 'Build-Tasks' + CartCleanup: 'Alte Warenkörbe bereinigen' + CartCleanupDesc: 'Löscht abgebrochene Warenkörbe aus dem system.' + CartTasks: 'Warenkorb-Aufgaben' + ClearCart: 'Den aktuellen Warenkorb löschen' + DebugCart: 'Warenkorb-Fehlersuche' + DeleteOrders: 'Alle Bestellungen löschen' + DeleteOrdersDesc: 'Alle Bestellungen, Modifikatoren und Zahlungen aus der Datenbank löschen.' + DeleteProducts: 'Alle Produkte löschen' + DeleteProductsDesc: 'Löscht alle Produkte aus der Datenbank.' + PopulateCart: 'Befülle den Warenkorb mit einigen erhältlichen Produkten' + PopulateShop: 'Befülle den Shop' + PopulateShopDesc: 'Befüllt den Shop mit Dummy-Produkten, Kategorien und erstellt andere notwendige Seiten.' + RecalculateOrders: 'Alle Bestellungen neu berechnen' + RecalculateOrdersDesc: 'Neu berechnen aller Bestellwerte. Warnung: Dies wird alle vorhergehenden Werte überschreiben.' + RunAllTests: 'Alle Shop-Tests starten' + UnitTests: 'Tests' + ShopEmail: + AdminNotificationSubject: 'Benachrichtigung für Bestellung Nr. {OrderNo}' + AdminNotificationTitle: 'Shop Benachrichtigung' + CancelSubject: 'Bestellung Nr. {OrderNo} wurde vom Kunden storniert' + ConfirmationSubject: 'Bestätigung der Bestellung Nr. {OrderNo}' + ConfirmationTitle: 'Bestellbestätigung' + ReceiptSubject: 'Bestellung Nr. {OrderNo} eingegangen.' + ReceiptTitle: 'Bestellungseingang' + StatusChangeTitle: 'Statusänderung' + StatusChanged: 'Statusänderung der Bestellung Nr. {OrderNo} zu "{OrderStatus}".' + ShopPeriodReport: + Description: 'Report über einen gewissen Zeitraum' + Title: 'Report über einen gewissen Zeitraum' + ShopQuantityField: + AddOne: '"{ItemTitle}" zum Warenkorb hinzufügen' + RemoveOne: '"{ItemTitle}" vom Warenkorb entfernen' + ShopSalesReport: + Description: 'Übersicht der Bestellungen für bestimmte Zeitperioden. Resultate können nach Jahr, Monat oder Tag gruppiert werden.' + Title: 'Shop Verkäufe' ShopSideReport: - ALLPRODUCTS: "Alle Produkte" - FEATUREDPRODUCTS: "Hervorgehobene Produkte" - HEAVY: "Schwere Produkte" - NOIMAGE: "Keine Produktabbildung vorhanden" - ShopGROUP: Shop + AllProducts: 'Alle Produkte' + FeaturedProducts: 'Hervorgehobene Produkte' + Heavy: 'Schwere Produkte' + NoImage: 'Produkte ohne Bild' + ShopGroup: Shop ShoppingCart: - CANNOTPURCHASE: "Das Produkt %s kann nicht gekauft werden." - CLEARED: "Der Warenkorb wurde erfolgreich gelöscht." - CSRF: "Ungültiger Sicherheitstoken, möglicher CSRF-Angriff." - ITEMADD: "Das Produkt wurde soeben aufgenommen." - ITEMNOTFOUND: "Produkt nicht gefunden." - ITEMREMOVED: "Produkt erfolgreich entfernt." - NOCARTFOUND: "Kein Warenkorb gefunden." - NOCARTINITIALISED: "kein Warenkorb initialisiert" - NOORDER: "Keine aktuelle Bestellung." - PRODUCTNOTFOUND: "Produkt nich gefunden." - QUANTITYSET: "Die Menge wurde eingegeben." - # Pretty generic translation, could be moved to a more generic category - CHANGE: "Wechsel" - ITEMS_IN_CART_PLURAL: "In Ihrem Warenkorb befinden sich {quantity} Artikel." - ITEMS_IN_CART_SINGULAR: "In Ihrem Warenkorb befindet sich 1 Artikel." - CHECKOUT: "Zur Kasse" - SideCart.ss: - HEADLINE: Warenkorb - NOITEMS: "Es befinden sich keine Produkte im Warenkorb" - READMORE: "Zeige \"%s\"" - REMOVEALL: "aus dem Warenkorb löschen" + CannotPurchase: '{Title} kann nicht gekauft werden.' + Checkout: Kasse + Cleared: 'Der Warenkorb wurde erfolgreich gelöscht.' + ContinueShopping: 'Den Einkauf fortsetzen' + Headline: 'Warenkorb' + InvalidSecurityToken: 'Ungültiger Sicherheitstoken, möglicher CSRF-Angriff.' + ItemAdded: 'Produkt erfolgreich hinzugefügt.' + ItemNotFound: 'Produkt nicht gefunden.' + ItemRemoved: 'Produkt erfolgreich entfernt.' + ItemsInCartPlural: 'In Ihrem Warenkorb befinden sich {quantity} Artikel.' + ItemsInCartSingular: 'In Ihrem Warenkorb befindet sich 1 Artikel.' + NoCartFound: 'Kein Warenkorb gefunden.' + NoCartInitialised: 'Kein Warenkorb initialisiert' + NoItems: 'Es befinden sich keine Produkte im Warenkorb.' + NoOrder: 'Keine aktuelle Bestellung.' + ProceedToCheckout: 'Zur Kasse' + ProductNotFound: 'Produkt nicht gefunden.' + QuantitySet: 'Die Menge wurde geändert.' + RemoveAllTitle: 'Entferne alle "{Title}" aus dem Warenkorb.' + RemoveTitle: 'Entferne "{Title}" aus dem Warenkorb.' + TableSummary: 'Im Warenkorb befindliche Produkte' SimpleShippingModifier: - PLURALNAME: Modifikatoren - SHIPTO: "Senden an %s" - SINGULARNAME: Versandkosten + PLURALNAME: 'Versandkosten Modifikatoren' + SINGULARNAME: Lieferung + ShipToCountry: 'Versand nach {Country}' db_Country: Land - SteppedCheckoutPage.ss: - NOITEMS: "Ihr Warenkorb ist leer." - SubGesamtModifier: - PLURALNAME: Zwischensumme - SINGULARNAME: Zwischensumme + SubTotalModifier: + PLURALNAME: 'Zwischensummen' + SINGULARNAME: 'Zwischensumme' TaxModifier: - ATRATE: "@ %s" + AtRate: 'zu {Rate}%' PLURALNAME: Steuern - SINGULARNAME: MwSt. - db_Rate: Rate - VariationsTable.ss: - ADD: "\"%s\" zum Warenkorb hinzufügen" - ADDLINK: "Diese Produkt zum Warenkorb hinzufügen" - QUANTITYCART: "Menge im Warenkorb" + SINGULARNAME: Steuer + db_Rate: Steuersatz + TaxReport: + Description: 'Zeigt alle bezahlten Steuern. Beinhaltet nur bezahlte Bestellungen.' + Title: Informationen zu Steuern + Variation: + SINGULARNAME: Variation + VariationForm: + ChooseAttribute: 'Wähle {attribute}…' + ProductNotAvailable: 'Das Produkt ist mit den ausgewählten Optionen nicht verfügbar.' + VariationNotAvailable: 'Diese Variante ist leider nicht verfügbar.' WeightShippingModifier: - PLURALNAME: Modifikatoren - SINGULARNAME: Versandkosten - TABLETITLE: "Versandkosten (%f kg)" + PLURALNAME: 'Versandkosten nach Gewicht' + SINGULARNAME: Versandkosten nach Gewicht + TableTitle: 'Versandkosten (für {Kilograms} kg)' Zone: PLURALNAME: Zonen SINGULARNAME: Zone - db_Description: Beschreibung + db_Description: Beschrieb db_Name: Name has_many_Regions: Regionen - ZoneAdmin: - MENUTITLE: Zonen ZoneRegion: - PLURALNAME: "Zonen Regionen" - SINGULARNAME: "Zonen Region" + PLURALNAME: 'Regionen' + SINGULARNAME: 'Region' has_one_Zone: Zone - shop: - Checkout: Zahlungsart - STATUS: Status - CartEditField: - REMOVE: "Entfernen" - VARIATION: "Variationen" - CartForm: - UPDATE_CART: "Warenkorb aktualisieren" - REMOVED_ITEMS: "{count} Artikel entfernt" - UPDATED_ITEMS: "{count} Artikel aktualisiert." - OnsitePaymentCheckoutComponent: - CREDIT_CARD_INVALID: "Kreditkarte ist ungültig" - PaymentCheckoutComponent: - NO_PAYMENT_METHOD: "Bezahlmethode wurde nicht angegeben" - UNSUPPORTED_GATEWAY: "Bezahl-Gateway wird nicht unterstützt" - CheckoutStep: - # These should probably go somewhere else, as they are pretty generic. - CONTINUE: "Weiter" - SHIPPING: "Lieferung" - SUMMARY: "Zusammenfassung" - GRAND_TOTAL: "Gesamtbetrag" - CheckoutStep_Address: - SUPPLY_CONTACT_INFO: "Bitte geben Sie Ihre Kontaktdaten an" - SEPARATE_BILLING: "Abweichende Rechnungsadresse angeben" - ENTER_SHIPPING_ADDRESS: "Bitte geben Sie Ihre gewünschte Lieferadresse ein." - BILL_TO: "Rechnung an:" - SHIP_TO: "Lieferung an:" - CheckoutStep_Membership: - CONTINUE_AS_GUEST: "Weiter als Gast" - # This is an option presented to the user - CREATE_ACCOUNT: "Neues Konto erstellen" - # This is an action (Button label) - CREATE_NEW_ACCOUNT: 'Konto erstellen' - VariationForm: - CHOOSE_ATTRIBUTE: "Wähle {attribute} …" - VARIATION_NOT_AVAILABLE: "Diese Variante ist leider nicht verfügbar." - PRODUCT_NOT_AVAILABLE: "Das Produkt ist mit den ausgewählten Optionen nicht verfügbar." - PriceTag: - # Save as in savings (you saved this much from the original price) - SAVE: "Gespart" diff --git a/lang/en.yml b/lang/en.yml index 724f63da6..367bea48a 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -1,521 +1,296 @@ ---- en: - AccountNavigation: - AddressBook: "Address Book" - EditProfile: "Edit Profile" - LogOut: "Log Out" + OrdersAdmin: + MENUTITLE: Orders + ProductCatalogAdmin: + MENUTITLE: Catalog + ZoneAdmin: + MENUTITLE: Zones + AbandonedCartReport: + Description: 'Monitor abandoned carts for a particular period. Group results by year, month, or day.' + Title: 'Abandoned Carts' + AccountPage: + AddressBook: 'Address Book' + DESCRIPTION: 'Allows the user to view his account-details and past orders.' + DefaultTitle: Account + EditProfile: 'Edit Profile' + LogOut: 'Log Out' + Login: "You'll need to login before you can access the account page. If you are not registered, you won't be able to access it until you make your first order, otherwise please enter your details below." + LoginAgain: 'You have been logged out. If you would like to log in again, please do so below.' MemberEmail: Email - MemberLastVisit: "Last Visit" + MemberLastVisit: 'Last Visit' MemberName: Name - MemberSince: "Member Since" - NumberOfOrders: "Number of orders" - PastOrders: "Past Orders" - Title: "My Account" - AccountPage: - DESCRIPTION: "Generic content page" - LOGIN: "You'll need to login before you can access the account page. If you are not registered, you won't be able to access it until you make your first order, otherwise please enter your details below." - LOGINAGAIN: "You have been logged out. If you would like to log in again, please do so below." - Message: "You'll need to login before you can access the account page. If you are not registered, you won't be able to access it until you make your first order, otherwise please enter your details below." - NOPAGE: "No AccountPage on this site - please create one !" - NoPastOrders: "No past orders found." - PLURALNAME: "Account Pages" - SINGULARNAME: "Account Page" - Title: "Past Orders" - NO_PAGE: "No AccountPage was found. Please create one in the CMS!" - AccountPage.ss: - COMPLETED: "Completed Orders" - HISTORY: "Your Order History" - INCOMPLETE: "Incomplete Orders" - NOCOMPLETED: "No completed orders were found." - NOINCOMPLETE: "No incomplete orders were found." - ORDER: Order - READMORE: "Read more on Order #%s" + MemberSince: 'Member Since' + NoPage: 'No AccountPage was found. Please create one in the CMS!' + NoPastOrders: 'No past orders found.' + NumberOfOrders: 'Number of orders' + PLURALNAME: 'Account Pages' + PastOrders: 'Past Orders' + SINGULARNAME: 'Account Page' + Title: 'My Account' AccountPage_AddressBook: - CreateNewTitle: "Create New Address" - NoAddress: "No addresses found." - Title: "Default Addresses" + CreateNewTitle: 'Create new address' + NoAddress: 'No addresses found.' + Title: 'Default addresses' + DefaultShippingAddress: 'Default Shipping Address' + DefaultBillingAddress: 'Default Billing Address' + MakeDefaultShipping: 'Make Default Shipping' + MakeDefaultShippingTitle: 'Make this my default shipping address' + MakeDefaultBilling: 'Make Default Billing' + MakeDefaultBillingTitle: 'Make this my default billing address' + DeleteAddress: 'Delete this address' AccountPage_EditProfile: - Title: "Edit Profile" - AccountPage_order.ss: - ADDRESS: Address - AMOUNT: Amount - BACKTOCHECKOUT: "Click here to go to the checkout page" - CITY: City - COUNTRY: Country - DATE: Date - DETAILS: Details - EMAILDETAILS: "A copy of this has been sent to your email address confirming the order details." - NAME: Name - PAYMENTMETHOD: Method - PAYMENTSTATUS: "Payment Status" - AddProductForm: - ADDTOCART: "Add to Cart" - Quantity: Quantity + Title: 'Edit Profile' Address: - ADDRESS: Address - ADDRESS2HINT: "premises, building, apartment, unit, floor" - ADDRESSHINT: "street / thoroughfare number, name, and type or P.O. Box" - ADDRESSLINE2: "Address Line 2 (optional)" - BillingAddress: "Billing Address" - CITY: City - CITYHINT: "or suburb, county, district" - COUNTRY: Country - PHONE: "Phone Number" + AddressHint: 'street / thoroughfare number, name, and type or P.O. Box' + AddressLine2Hint: 'premises, building, apartment, unit, floor' + BillingAddress: 'Billing Address' + CityHint: 'or suburb, county, district' + CreateNewAddress: 'Create new address' + ExistingBillingAddress: 'Existing Billing Address' + ExistingShippingAddress: 'Existing Shipping Address' PLURALNAME: Addresses - POSTALCODE: "Postal Code" SINGULARNAME: Address - STATE: State - STATEHINT: "or province, territory, island" - SaveDefaults: "Save Defaults" - SaveNew: "Save New Address" - ShippingAddress: "Shipping Address " + SaveDefaults: 'Save Defaults' + SaveNew: 'Save New Address' + ShippingAddress: 'Shipping Address' + StateHint: 'or province, territory, island' db_Address: Address - db_AddressLine2: "Address Line2" + db_AddressLine2: 'Address Line 2 (optional)' db_City: City db_Company: Company db_Country: Country - db_FirstName: "First Name" - db_Latitude: Latitude - db_Longitude: Longitude - db_Phone: Phone - db_PostalCode: "Postal Code" + db_FirstName: 'First Name' + db_Phone: 'Phone Number' + db_PostalCode: 'Postal Code' db_State: State db_Surname: Surname has_one_Member: Member - AddressBookCheckoutComponent: - CREATENEWADDRESS: "Create new address" - EXISTING_BILLING_ADDRESS: "Existing Billing Address" - EXISTING_SHIPPING_ADDRESS: "Existing Shipping Address" - EXISTING_ADDRESS: "Existing Address" - Cart.ss: - ADDONE: "Add one more of \"%s\" to your cart" - CheckoutClick: "Click here to go to the checkout" - CheckoutGoTo: "Go to checkout" - HEADLINE: "My Cart" - NOITEMS: "There are no items in your cart." - PRICE: Price - PRODUCT: Product - QUANTITY: Quantity - READMORE: "Click here to read more on \"%s\"" - REMOVE: "Remove \"%s\" from your order" - REMOVEALL: "Remove all of \"%s\" from your cart" - REMOVEONE: "Remove one of \"%s\" from your cart" - SUBTOTAL: Sub-total - TABLESUMMARY: "Current contents of your cart." - TOTAL: Total - TOTALPRICE: "Total Price" - UNITPRICE: "Unit Price" + CartForm: + REMOVED_ITEMS: 'Removed {count} items.' + UPDATED_ITEMS: 'Updated {count} items.' + UpdateCart: 'Update Cart' CartPage: - DESCRIPTION: "Generic content page" - PLURALNAME: "Cart Pages" - SINGULARNAME: "Cart Page" - TITLE: "Shopping Cart" - has_one_CheckoutPage: "Checkout Page" - has_one_ContinuePage: "Continue Page" - CartPage.ss: - CARTEMPTY: "Your cart is empty." - CONTINUE: "Continue Shopping" - PROCEEDTOCHECKOUT: "Proceed to Checkout" + DESCRIPTION: 'A page where the user will be able to see and edit his shopping-cart contents.' + DefaultTitle: 'Shopping Cart' + PLURALNAME: 'Cart Pages' + SINGULARNAME: 'Cart Page' + has_one_CheckoutPage: 'Checkout Page' + has_one_ContinuePage: 'Continue Page' Checkout: - IDFIELDNOTFOUND: "Required field not found: %s" - MEMBEREXISTS: "A member already exists with the %s %s" - MEMBERSHIPSNOTALLOWED: "Creating new memberships is not allowed" - NOPAYMENTMETHOD: "Payment method does not exist" - PASSWORDREQUIRED: "A password is required" + IdFieldNotFound: 'Required field not found: {IdentifierField}' + MemberExists: 'A member already exists with the {Field} {Identifier}' + MembershipIsNotAllowed: 'Creating new memberships is not allowed' + NoPaymentMethod: 'Payment method does not exist' + PasswordRequired: 'A password is required' + TermsAndConditionsLink: 'I agree to the terms and conditions stated on the {TermsPageTitle} page' CheckoutComponentValidator: - INVALIDMESSAGE: "There are problems with the data you entered. See below:" + InvalidDataMessage: 'There are problems with the data you entered. See below:' CheckoutField: - ACCOUNTINFO: "Please choose a password, so you can login and check your order history in the future" - EMAIL: Email - FIRSTNAME: "First Name" - MEMBERINFO: "If you are already a member please" - MEMBERSHIPDETAILS: "Membership Details" - MUSTAGREETOTERMS: "You must agree to the terms and conditions" - NOTES: Message - PASSWORD: Password - SURNAME: Surname - TERMSANDCONDITIONS: "I agree to the terms and conditions stated on the terms and conditions page" - PAYMENT_TYPE: "Payment Type" - CheckoutFields: - PAYMENTTYPE: "Payment Type" - CheckoutForm: - PROCEED: "Proceed to payment" + AccountInfo: 'Please choose a password, so you can login and check your order history in the future' + MemberLoginInfo: 'If you are already a member please log in' + MembershipDetails: 'Membership Details' + MustAgreeToTerms: 'You must agree to the terms and conditions' + Password: Password + PaymentType: 'Payment Type' CheckoutPage: - DESCRIPTION: "Generic content page" - NOPAGE: "No CheckoutPage on this site - please create one!" - PLURALNAME: "Checkout Pages" - PaymentErrorMessage: "Received error from payment gateway:" - SINGULARNAME: "Checkout Page" - TITLE: Checkout - db_PurchaseComplete: "Purchase Complete" - PURCHASE_COMPLETE_DESCRIPTION: "This message is included in reciept email, after the customer submits the checkout" - SUBMIT_PAYMENT: "Submit Payment" - CheckoutPage.ss: - CARTEMPTY: "Your cart is empty." - CHECKOUT: Checkout - ORDERSTATUS: "Order Status" - PROCESS: Process - ChequePayment: - MESSAGE: "Payment accepted via Cheque. Please note : products will not be shipped until payment has been received." - PLURALNAME: "Cheque Payments" - SINGULARNAME: "Cheque Payment" + DESCRIPTION: 'Page that handles the checkout-process.' + DefaultTitle: Checkout + PLURALNAME: 'Checkout Pages' + PaymentErrorMessage: 'Received error from payment gateway:' + ProceedToPayment: 'Proceed to payment' + PurchaseCompleteDescription: 'This message is included in reciept email, after the customer submits the checkout.' + SINGULARNAME: 'Checkout Page' + SubmitPayment: 'Submit Payment' + db_PurchaseComplete: 'Purchase Complete' + CheckoutStep: + Continue: Continue + Shipping: Shipping + Summary: Summary + CheckoutStep_Address: + BillTo: 'Bill To:' + EnterShippingAddress: 'Please enter your shipping address details.' + SeperateBilling: 'Bill to a different address from this' + ShipTo: 'Ship To:' + SupplyContactInformation: 'Supply your contact information' + CheckoutStep_Membership: + ContinueAsGuest: 'Continue as Guest' + CreateAccount: 'Create an Account' + CreateNewAccount: 'Create New Account' CreateAddressForm: - SAVED: "Your address has been saved" - DPSPayment: - PLURALNAME: "D P S Payments" - SINGULARNAME: "D P S Payment" - EwayXMLPayment: - PLURALNAME: "Eway X M L Payments" - SINGULARNAME: "Eway X M L Payment" + AddressSaved: 'Your address has been saved' + CustomerReport: + Description: 'Customers report' + Title: Customers FlatTaxModifier: PLURALNAME: Taxes SINGULARNAME: Tax FreeShippingModifier: - FREE: FREE - PLURALNAME: Modifiers + Free: FREE + PLURALNAME: 'Free Shipping Modifiers' SINGULARNAME: Shipping - General: - DATEFORMATLONG: "%m/%d/%G %I:%M%p" - DATEFORMATNICE: "%m/%d/%G" - DATEFORMATSHORT: "%m/%d/%G" - DATETIMEFORMATNICE: "%m/%d/%G %I:%M%p" - DATETIMEFORMATNICE24: "d/m/Y H:i" GlobalTaxModifier: - INCLUDED: " (included in the above price)" + Included: ' (included in the above price)' PLURALNAME: Taxes SINGULARNAME: Tax db_Country: Country - Member: - BUTTONCHANGEPASSWORD: "Change Password" - CONFIRMNEWPASSWORD: "Confirm New Password" - NEWPASSWORD: "New Password" - YOUROLDPASSWORD: "Your old password" MemberForm: - DETAILSSAVED: "Your details have been saved" - LOGGEDIN: "You are currently logged in as " - SAVE: "Save Changes" - SAVEANDPROCEED: "Save and proceed to checkout" - ORDER: - INVOICE: Invoice + DetailsSaved: 'Your details have been saved' + Save: 'Save Changes' + SaveAndProceed: 'Save and proceed to checkout' + OnsitePaymentCheckoutComponent: + InvalidCreditCard: 'Credit card is invalid' Order: - CANCELSUBJECT: "Order #%d cancelled by member" - INCOMPLETE: "Order Incomplete" + BillTo: 'Bill To' + Date: Date + DateFrom: 'Date from' + DateTo: 'Date to' + GrandTotal: 'Grand Total' + Invoice: Invoice + OrderHeadline: 'Order #{OrderNo} {OrderDate}' + Outstanding: Outstanding + OutstandingWithAmount: 'Outstanding: {Amount}' PLURALNAME: Orders - PRINT: Print + Print: Print + Quantity: Quantity SINGULARNAME: Order - SUCCESSFULL: "Order Successful" - DATE_FROM: "Date from" - DATE_TO: "Date to" - db_AnalyticsSubmitted: "Analytics Submitted" + STATUS_ADMINCANCELLED: 'Cancelled by admin' + STATUS_CART: Cart + STATUS_COMPLETE: Complete + STATUS_MEMBERCANCELLED: 'Cancelled by member' + STATUS_PAID: Paid + STATUS_PROCESSING: Processing + STATUS_SENT: Sent + STATUS_UNPAID: Unpaid + ShipTo: 'Ship To' + SubTotal: Sub-total + Total: Total + TotalOutstanding: 'Total outstanding' + TotalPrice: 'Total Price' + TotalPriceWithCurrency: 'Total Price ({Currency})' + UnitPrice: 'Unit Price' db_Dispatched: Dispatched db_Email: Email - db_FirstName: "First Name" - db_IPAddress: IPAddress - db_Notes: Notes + db_FirstName: 'First Name' + db_IPAddress: 'IP Address' + db_Notes: Message db_Paid: Paid db_Placed: Placed db_Printed: Printed - db_ReceiptSent: "Receipt Sent" + db_ReceiptSent: 'Receipt Sent' db_Reference: Reference - db_SeparateBillingAddress: "Separate Billing Address" - db_ShippingTotal: "Shipping Total" + db_SeparateBillingAddress: 'Separate Billing Address' db_Status: Status db_Surname: Surname db_Total: Total has_many_Items: Items has_many_Modifiers: Modifiers - has_many_OrderStatusLogs: "Order Status Logs" + has_many_OrderStatusLogs: 'Order Status Logs' has_many_Payments: Payments - has_one_BillingAddress: "Billing Address" + has_one_BillingAddress: 'Billing Address' has_one_Member: Member - has_one_ShippingAddress: "Shipping Address" - has_one_ShippingMethod: "Shipping Method" - Order.ss: - ORDERNOTES: Notes - TOTALOUTSTANDING: "Total outstanding" + has_one_ShippingAddress: 'Shipping Address' OrderActionsForm: - CANCELORDER: "Cancel this order" - MAKEPAYMENT: "Make Payment" - OUTSTANDING: "Outstanding: %s" - PAYMENTMETHOD: "Payment Method" - PAYORDER: "Pay outstanding balance" - MANUAL_NOT_ALLOWED: "Manual payment not allowed" - OrderAdmin_Addresses.ss: - ADDRESS: Address - BILLTO: "Bill To" - SHIPTO: "Ship To" - OrderAdmin_Content.ss: - ITEMS: Items - PRODUCT: Product - QUANTITY: Quantity - TOTALPRICE: "Total Price" - UNITPRICE: "Unit Price" - OrderAdmin_Content_ItemLine.ss: - READMORE: "View \"%s\"" - OrderAdmin_Content_SubTotals.ss: - SUBTOTAL: Sub-total - TOTAL: Total - TOTALOUTSTANDING: Outstanding - OrderAdmin_Customer.ss: - CUSTOMER: Customer - OrderAdmin_Notes.ss: - NOTES: Notes - OrderAdmin_Printable.ss: - ORDER: Order + CancelOrder: 'Cancel this order' + MakePayment: 'Make Payment' + ManualNotAllowed: 'Manual payment not allowed' + PayOrder: 'Pay outstanding balance' + PaymentMethod: 'Payment Method' + OrderAdmin: + ReceiptTitle: '{SiteTitle} Order {OrderNo}' OrderAttribute: PLURALNAME: Attributes SINGULARNAME: Attribute - db_CalculatedTotal: "Calculated Total" + db_CalculatedTotal: 'Calculated Total' has_one_Order: Order OrderForm: - COULDNOTPROCESSPAYMENT: "Payment could not be processed." - LogIn: "log in" - ORDERCANCELLED: "Order sucessfully cancelled" - OrderHistory: - OrderDate: Date - OrderItems: Items - OrderReference: Reference - OrderStatus: Status - OrderTotal: Total - ViewOrder: view - OrderInformation.ss: - ADDRESS: Address - AMOUNT: Amount - BUYERSADDRESS: "Buyer's Address" - CITY: City - COUNTRY: Country - CUSTOMERDETAILS: "Customer Details" - DATE: Date - DETAILS: Details - EMAIL: Email - MOBILE: Mobile - NAME: Name - ORDERSUMMARY: "Order Summary" - PAYMENTID: "Payment ID" - PAYMENTINFORMATION: "Payment Information" - PAYMENTMETHOD: Method - PAYMENTSTATUS: "Payment Status" - PHONE: Phone - PRICE: Price - PRODUCT: Product - QUANTITY: Quantity - READMORE: "Click here to read more on \"%s\"" - SHIPPINGDETAILS: "Shipping Details" - SUBTOTAL: Sub-total - TOTAL: Total - TOTALOUTSTANDING: "Total outstanding" - TOTALPRICE: "Total Price" - OrderInformation_Editable.ss: - ADDONE: "Add one more of \"%s\" to your cart" - NOITEMS: "There are no items in your cart." - ORDERINFORMATION: "Order Information" - PRICE: Price - PRODUCT: Product - QUANTITY: Quantity - READMORE: "Click here to read more on \"%s\"" - REMOVE: "Remove \"%s\" from your order" - REMOVEALL: "Remove all of \"%s\" from your cart" - REMOVEONE: "Remove one of \"%s\" from your cart" - SUBTOTAL: Sub-total - TABLESUMMARY: "The contents of your cart are displayed in this form and summary of all fees associated with an order and a rundown of payments options." - TOTAL: Total - TOTALPRICE: "Total Price" - OrderInformation_NoPricing.ss: - ADDRESS: Address - BUYERSADDRESS: "Buyer's Address" - CITY: City - COUNTRY: Country - CUSTOMERDETAILS: "Customer Details" - EMAIL: Email - MOBILE: Mobile - NAME: Name - ORDERINFO: "Information for Order #" - PHONE: Phone - TABLESUMMARY: "The contents of your cart are displayed in this form and summary of all fees associated with an order and a rundown of payments options." - OrderInformation_PackingSlip.ss: - DESCRIPTION: Description - ITEM: Item - ORDERDATE: "Order Date" - ORDERNUMBER: "Order Number" - PAGETITLE: "Shop Print Orders" - QUANTITY: Quantity - TABLESUMMARY: "The contents of your cart are displayed in this form and summary of all fees associated with an order and a rundown of payments options." - OrderInformation_Print.ss: - PAGETITLE: "Print Orders" + CouldNotProcessPayment: 'Payment could not be processed.' + OrderCancelled: 'Order sucessfully cancelled' OrderItem: PLURALNAME: Items SINGULARNAME: Item db_Quantity: Quantity - db_UnitPrice: "Unit Price" + db_UnitPrice: 'Unit Price' OrderModifier: PLURALNAME: Modifiers SINGULARNAME: Modifier db_Amount: Amount db_Sort: Sort db_Type: Type - OrderNotifier: - ADMINNOTIFICATIONSUBJECT: "Order #%d Notification" - CONFIRMATIONSUBJECT: "Order #%d Confirmation" - RECEIPTSUBJECT: "Order #%d Receipt" OrderProcessor: - NULL: "A new order has not yet been started." - NOITEMS: "Order has no items." - NOTCART: "Order is not a cart." - OrderReport: - CHANGESTATUS: "Change Order Status" - NOTEEMAIL: Note/Email - SENDNOTETO: "Send this note to %s (%s)" + NoItems: 'Order has no items.' + NoOrder: 'Order does not exist.' + NoOrderStarted: 'A new order has not yet been started.' + NotCart: 'Order is not a cart.' OrderStatusLog: - PLURALNAME: "Order Status Log Entries" - SINGULARNAME: "Order Log Entry" - db_DispatchTicket: "Dispatch Ticket" - db_DispatchedBy: "Dispatched By" - db_DispatchedOn: "Dispatched On" + PLURALNAME: 'Order Status Log Entries' + SINGULARNAME: 'Order Log Entry' + db_DispatchTicket: 'Dispatch Ticket' + db_DispatchedBy: 'Dispatched By' + db_DispatchedOn: 'Dispatched On' db_Note: Note - db_PaymentCode: "Payment Code" - db_PaymentOK: "Payment OK" - db_SentToCustomer: "Sent To Customer" + db_PaymentCode: 'Payment Code' + db_PaymentOK: 'Payment OK' + db_SentToCustomer: 'Sent To Customer' db_Title: Title has_one_Author: Author has_one_Order: Order - Order_Address.ss: - BILLTO: "Bill To" - SHIPTO: "Ship To" - Order_AdminNotificationEmail.ss: - TITLE: "Shop Receipt" - Order_ConfirmationEmail.ss: - TITLE: "Order Confirmation" - Order_Content.ss: - NOITEMS: "There are no items in your order." - PRICE: Price - PRODUCT: Product - QUANTITY: Quantity - READMORE: "Click here to read more on \"%s\"" - SUBTOTAL: Sub-total - TOTAL: Total - TOTALOUTSTANDING: "Total outstanding" - TOTALPRICE: "Total Price" - UNITPRICE: "Unit Price" - Order_Content_ItemLine.ss: - READMORE: "View \"%s\"" - Order_Content_SubTotals.ss: - SUBTOTAL: Sub-total - TOTAL: Total - Order_ItemLine.ss: - READMORE: "View \"%s\"" - Order_Member.ss: - ADDRESS: Address - CITY: City - COUNTRY: Country - EMAIL: Email - MOBILE: Mobile - NAME: Name - PHONE: Phone - Order_Payments.ss: - AMOUNT: Amount - DATE: Date - PAYMENTMETHOD: Method - PAYMENTNOTE: Note - PAYMENTSTATUS: "Payment Status" - Order_ReceiptEmail.ss: - HEADLINE: "Shop Order Receipt" - TITLE: "Shop Receipt" - Order_StatusEmail.ss: - HEADLINE: "Shop Status Change" - STATUSCHANGE: "Status changed to \"%s\" for Order #" - TITLE: "Shop Status Change" - OrdersAdmin: - MENUTITLE: Orders - PayPalPayment: - PLURALNAME: "Pay Pal Payments" - SINGULARNAME: "Pay Pal Payment" Payment: - AMOUNT: Amount - PAYMENTTYPE: "Payment Type" - PLURALNAME: Payments - SINGULARNAME: Payment - SUBTOTAL: Subtotal - PaymentInformation.ss: - AMOUNT: Amount - DATE: Date - DETAILS: Details - PAYMENTID: "Payment ID" - PAYMENTINFORMATION: "Payment Information" - PAYMENTMETHOD: Method - PAYMENTSTATUS: "Payment Status" - TABLESUMMARY: "The contents of your cart are displayed in this form and summary of all fees associated with an order and a rundown of payments options." + Note: Note + PaymentsHeadline: Payment(s) + PaymentCheckoutComponent: + NoPaymentMethod: 'Payment method not provided' + UnsupportedGateway: 'Gateway not supported' PaymentProcessor: - INVALID_GATEWAY: "`{gateway}` isn't a valid payment gateway." - CANTPAY: "Order can't be paid for." - PaystationHostedPayment: - PLURALNAME: "Paystation Hosted Payments" - SINGULARNAME: "Paystation Hosted Payment" - PaystationPayment: - PLURALNAME: "Paystation Payments" - SINGULARNAME: "Paystation Payment" + CantPay: "Order can't be paid for." + InvalidGateway: "`{gateway}` isn't a valid payment gateway." PickupShippingModifier: PLURALNAME: Modifiers - SINGULARNAME: "Pick Up Shipping" + SINGULARNAME: 'Pick Up Shipping' + PriceTag: + SAVE: Save Product: - ADDITIONALCATEGORIES: "Additional Categories" - ALLOWPURCHASE: "Allow product to be purchased" - CATEGORY: Category - CATEGORYDESCRIPTION: "This is the parent page or default category." - CODE: "Product Code/SKU" - CODE_SHORT: "SKU" - COSTPRICE: "Cost Price" - COSTPRICEDESC: "Wholesale price before markup." - DEPTH: "Depth (%s)" - DESCRIPTION: "Generic content page" - FEATURED: "Featured Product" - HEIGHT: "Height (%s)" - IMAGE: "Product Image" - MODEL: Model - PAGETITLE: "Product Title" + AddToCart: 'Add to Cart' + AddToCartTitle: 'Add "{Title}" to your cart' + AdditionalCategories: 'Additional Categories' + AllowPurchase: 'Allow product to be purchased' + Category: Category + CategoryDescription: 'This is the parent page or default category.' + Code: 'Product Code' + CostPriceDescription: 'Wholesale price before markup.' + DESCRIPTION: 'Product page in the shop' + Featured: 'Featured Product' + Image: 'Product Image' + ImageAltText: '{Title} image' + InternalItemID: 'Product Code/SKU' + Model: Model + NoImage: 'no image' PLURALNAME: Products - PRICE: Price - PRICEDESC: "Base price to sell this product at." + PageTitle: 'Product Title' + Price: Price + PriceDesc: 'Base price to sell this product at.' + ProductCodeShort: SKU SINGULARNAME: Product - WEIGHT: "Weight (%s)" - WIDTH: "Width (%s)" - NO_IMAGE: "no image" - db_AllowPurchase: "Allow Purchase" - db_BasePrice: "Base Price" - db_CostPrice: "Cost Price" + Size: Size + View: 'View Product' + db_AllowPurchase: 'Allow Purchase' + db_BasePrice: Price + db_CostPrice: 'Cost Price' db_Depth: Depth db_Featured: Featured db_Height: Height - db_InternalItemID: "Internal Item ID" + db_InternalItemID: 'Internal Item ID' db_Model: Model db_Popularity: Popularity db_Weight: Weight db_Width: Width has_many_Variations: Variations has_one_Image: Image - many_many_ProductCategories: "Product Categories" - many_many_VariationAttributeTypes: "Variation Attribute Types" - Product.ss: - ADD: "Add \"%s\" to your cart" - ADDLINK: "Add this item to cart" - ADDONE: "Add one more of \"%s\" to your cart" - AUTHOR: Author - CODE: "Product Code" - FEATURED: "This is a featured product." - GOTOCHECKOUT: "Go to the checkout now" - GOTOCHECKOUTLINK: "» Go to the checkout" - IMAGE: "%s image" - ItemID: "Item #" - MODEL: Model - NOIMAGE: "Sorry, no product image for \"%s\"" - QUANTITYCART: "Quantity in cart" - REMOVE: "Remove \"%s\" from your cart" - REMOVEALL: "Remove one of \"%s\" from your cart" - REMOVELINK: "» Remove from cart" - SIZE: Size + many_many_ProductCategories: 'Product Categories' + many_many_VariationAttributeTypes: 'Variation Attribute Types' ProductAttributeType: PLURALNAME: Attributes SINGULARNAME: Attribute - SAVE_FIRST_MESSAGE: "Save first, then you can add values." + SaveFirstInfo: 'Save first, then you can add values.' belongs_many_many_Product: Product db_Label: Label db_Name: Name @@ -523,248 +298,197 @@ en: ProductAttributeValue: PLURALNAME: Values SINGULARNAME: Value - belongs_many_many_ProductVariation: "Product Variation" + belongs_many_many_ProductVariation: 'Product Variation' db_Sort: Sort db_Value: Value has_one_Type: Type - ProductCatalogAdmin: - MENUTITLE: Catalog ProductCategory: - DESCRIPTION: "Generic content page" + DESCRIPTION: 'Product category in the shop' PLURALNAME: Categories SINGULARNAME: Category belongs_many_many_Products: Products - ProductCategory.ss: - FEATURED: "Featured Products" - OTHER: "Other Products" - ProductCategoryItem.ss: - ADD: "Add \"%s\" to your cart" - ADDLINK: "Add this item to cart" - ADDONE: "Add one more of \"%s\" to your cart" - AUTHOR: Author - GOTOCHECKOUT: "Go to the checkout now" - GOTOCHECKOUTLINK: "» Go to the checkout" - IMAGE: "%s image" - NOIMAGE: "Sorry, no product image for \"%s\"" - QUANTITYCART: "Quantity in cart" - READMORE: "Click here to read more on \"%s\"" - READMORECONTENT: "Click to read more »" - REMOVE: "Remove \"%s\" from your cart" - REMOVEALL: "Remove one of \"%s\" from your cart" - REMOVELINK: "» Remove from cart" + Alphabetical: Alphabetical + Price: Price ProductGroup: - NEXT: next - PAGE: page - PREVIOUS: previous - VIEW_PREVIOUS: "View the previous page" - VIEW_NEXT: "View the next page" - VIEW_PAGE: "View page number {pageNum}" - ProductGroupItem.ss: - ADD: "Add \"%s\" to your cart" - ADDLINK: "Add to Cart" - IMAGE: "%s image" - MODEL: Model - READMORE: "Click here to read more on \"%s\"" - VIEW: "View Product" - ProductMenu.ss: - GOTOPAGE: "Go to the %s page" + Next: next + Page: Page + Previous: previous + ViewNext: 'View the next page' + ViewPage: 'View page number {pageNum}' + ViewPrevious: 'View the previous page' + ProductMenu: + GotoPageTitle: 'Go to the {Title} page' + ProductReport: + Description: "Understand which products are performing, and which aren't." + Title: Products ProductVariation: + MustSaveFirstMessage: 'You can choose variation attributes after saving for the first time, if they exist.' + NoAttributeValuesMessage: '{attribute} has no values to choose from. You can create them in the "Products" > "Product Attribute Type" section of the CMS.' PLURALNAME: Variations SINGULARNAME: Variation - db_InternalItemID: "Internal Item ID" + db_InternalItemID: 'Internal Item ID' db_Price: Price db_Version: Version has_one_Image: Image has_one_Product: Product - many_many_AttributeValues: "Attribute Values" - NO_ATTRIBUTE_VALUES_MESSAGE: "{attribute} has no values to choose from. You can create them in the \"Products\" > \"Product Attribute Type\" section of the CMS." - SAVE_FIRST_MESSAGE: "You can choose variation attributes after saving for the first time, if they exist." - ProductVariationsExtension: - ATTRIBUTES: "Attributes" - ATTRIBUTES_DESCRIPTION: "These are fields to indicate the way(s) each variation varies. Once selected, they can be edited on each variation." - VARIATIONS: "Variations" - VARIATIONS_INSTRUCTIONS: "Price - Because you have one or more variations, the price can be set in the \"Variations\" tab." + many_many_AttributeValues: 'Attribute Values' ProductVariation_OrderItem: PLURALNAME: Items SINGULARNAME: Item - db_ProductVariationVersion: "Product Variation Version" - has_one_ProductVariation: "Product Variation" + db_ProductVariationVersion: 'Product Variation Version' + has_one_ProductVariation: 'Product Variation' + ProductVariationsExtension: + Attributes: Attributes + AttributesDescription: 'These are fields to indicate the way(s) each variation varies. Once selected, they can be edited on each variation.' + Variations: Variations + VariationsInfo: 'Price - Because you have one or more variations, the price can be set in the "Variations" tab.' Product_OrderItem: PLURALNAME: Items SINGULARNAME: Item - db_ProductVersion: "Product Version" + db_ProductVersion: 'Product Version' has_one_Product: Product RegionRestriction: - PLURALNAME: "Region Restrictions" - SINGULARNAME: "Region Restriction" + PLURALNAME: 'Region Restrictions' + SINGULARNAME: 'Region Restriction' db_City: City db_Country: Country - db_PostalCode: "Postal Code" + db_PostalCode: 'Postal Code' db_State: State SetLocationForm: - CHOOSECOUNTRY: "Choose country..." - COUNTRY: Country + ChooseCountry: 'Choose country...' + Country: Country ShippingModifier: PLURALNAME: Modifiers SINGULARNAME: Shipping Shop: - DEVTOOLSTITLE: "Shop Development Tools" + Change: Change + Customer: Customer + Date: Date + DateFormatNice: '%m/%d/%G' + DateTimeFormatNice: '%m/%d/%G %I:%M%p' + DateTimeFormatNice24: 'd/m/Y H:i' + DevToolsTitle: 'Shop Development Tools' + Edit: Edit + Quantity: Quantity + ReadMoreTitle: 'Click here to read more on "{Title}"' + Remove: Remove + View: view ShopConfig: - CUSTOMERGROUP: "Group to add new customers to" - DEFAULTIMAGE: "Default Product Image" - TERMSPAGE: "Terms and Conditions Page" - ALLOWED_COUNTRIES: "Allowed Ordering and Shipping Countries" - ALLOWED_COUNTRIES_TAB_TITLE: "Allowed Countries" + AllowedCountries: 'Allowed Ordering and Shipping Countries' + AllowedCountriesTabTitle: 'Allowed Countries' + CustomerGroup: 'Group to add new customers to' + DefaultImage: 'Default Product Image' + TermsPage: 'Terms and Conditions Page' ShopCurrency: - FREE: "FREE" + Free: 'FREE' ShopDashboard: - RECENTORDERS: "Recent Orders" - RECENTORDERSDESCRIPTION: "Shows recent orders" - RecentAccounts: "Recent Accounts" - RecentAccountsDescription: "Shows recent account signups" - RecentOrdersChart: "Order History Chart" - RecentOrdersChartDescription: "Shows recent orders on a graph" - NUMBER_OF_ACCOUNTS: "Number of accounts to show" - NUMBER_OF_DAYS: "Number of days to show" - NUMBER_OF_ORDERS: "Number of orders to show" - # These should probably be moved into a more generic category, since they can be used in a lot of places - EDIT: "Edit" - CUSTOMER: "Customer" - DATE: "Date" - ShopDatabaseAdmin.ss: - BUILDTASKS: "Build Tasks" - CARTCLEANUP: "Cleanup old carts" - CARTCLEANUPDESC: "Remove abandoned carts." - CARTTASKS: "Cart tasks" - DEBUGCART: "Debug the shopping cart" - RECALCULATEORDERS: "Recalculate All Orders" - RECALCULATEORDERSDESC: "Recalculate all order values. Warning: this will overwrite any historical values." - RUNALLTESTS: "Run all shop unit tests" - UNITTESTS: "Unit Tests" - ShopDevelopmentAdmin.ss: - BUILDTASKS: "Build Tasks" - CARTCLEANUP: "Cleanup old carts" - CARTCLEANUPDESC: "Remove abandoned carts." - CARTTASKS: "Cart tasks" - CLEARCART: "Clear the current shopping cart" - DEBUGCART: "Debug the shopping cart" - DELETEORDERS: "Delete All Orders" - DELETEORDERSDESC: "Remove all orders, modifiers, and payments from the database." - DELETEPRODUCTS: "Delete All Products" - DELETEPRODUCTSDESC: "Remove all products from the database." - POPULATECART: "Populate cart with some available products" - POPULATESHOP: "Populate shop" - POPULATESHOPDESC: "Populate the shop with dummy products, categories, and other necessary pages." - RECALCULATEORDERS: "Recalculate All Orders" - RECALCULATEORDERSDESC: "Recalculate all order values. Warning: this will overwrite any historical values." - RUNALLTESTS: "Run all shop unit tests" - UNITTESTS: "Unit Tests" - ShopQuantityField.ss: - ADDONE: "Add one more of \"%s\" to your cart" - REMOVEONE: "Remove one of \"%s\" from your cart" + NumberOfAccounts: 'Number of accounts to show' + NumberOfDays: 'Number of days to show' + NumberOfOrders: 'Number of orders to show' + RecentAccounts: 'Recent Accounts' + RecentAccountsDescription: 'Shows recent account signups' + RecentOrders: 'Recent Orders' + RecentOrdersChart: 'Order History Chart' + RecentOrdersChartDescription: 'Shows recent orders on a graph' + RecentOrdersDescription: 'Shows recent orders' + ShopDevelopmentAdmin: + BuildTasks: 'Build Tasks' + CartCleanup: 'Cleanup old carts' + CartCleanupDesc: 'Remove abandoned carts.' + CartTasks: 'Cart tasks' + ClearCart: 'Clear the current shopping cart' + DebugCart: 'Debug the shopping cart' + DeleteOrders: 'Delete all orders' + DeleteOrdersDesc: 'Remove all orders, modifiers, and payments from the database.' + DeleteProducts: 'Delete all products' + DeleteProductsDesc: 'Remove all products from the database.' + PopulateCart: 'Populate cart with some available products' + PopulateShop: 'Populate shop' + PopulateShopDesc: 'Populate the shop with dummy products, categories, and other necessary pages.' + RecalculateOrders: 'Recalculate all orders' + RecalculateOrdersDesc: 'Recalculate all order values. Warning: this will overwrite any historical values.' + RunAllTests: 'Run all shop unit tests' + UnitTests: 'Unit Tests' + ShopEmail: + AdminNotificationSubject: 'Order #{OrderNo} notification' + AdminNotificationTitle: 'Shop Receipt' + CancelSubject: 'Order #{OrderNo} cancelled by member' + ConfirmationSubject: 'Order #{OrderNo} confirmation' + ConfirmationTitle: 'Order Confirmation' + ReceiptSubject: 'Order #{OrderNo} receipt' + ReceiptTitle: 'Shop Receipt' + StatusChangeTitle: 'Shop Status Change' + StatusChanged: 'Status for order #{OrderNo} changed to "{OrderStatus}"' + ShopPeriodReport: + Description: 'Shop Period Report' + Title: 'Shop Period Report' + ShopQuantityField: + AddOne: 'Add one more of "{ItemTitle}" to your cart' + RemoveOne: 'Remove one of "{ItemTitle}" from your cart' + ShopSalesReport: + Description: 'Monitor shop sales performance for a particular period. Group results by year, month, or day.' + Title: 'Shop Sales' ShopSideReport: - ALLPRODUCTS: "All Products" - FEATUREDPRODUCTS: "Featured Products" - HEAVY: "Heavy Products" - NOIMAGE: "Products with no image" - ShopGROUP: Shop + AllProducts: 'All Products' + FeaturedProducts: 'Featured Products' + Heavy: 'Heavy Products' + NoImage: 'Products with no image' + ShopGroup: Shop ShoppingCart: - CANNOTPURCHASE: "This %s cannot be purchased." - CLEARED: "Cart was successfully cleared." - CSRF: "Invalid security token, possible CSRF attack." - ITEMADD: "Item has been added successfully." - ITEMNOTFOUND: "Item not found." - ITEMREMOVED: "Item has been successfully removed." - NOCARTFOUND: "No cart found." - NOCARTINITIALISED: "no cart initialised" - NOORDER: "No current order." - PRODUCTNOTFOUND: "Product not found." - QUANTITYSET: "Quantity has been set." - # Pretty generic translation, could be moved to a more generic category - CHANGE: "Change" - ITEMS_IN_CART_PLURAL: "There are {quantity} items in your cart." - ITEMS_IN_CART_SINGULAR: "There is 1 item in your cart." - CHECKOUT: "Checkout" - SideCart.ss: - HEADLINE: "My Cart" - NOITEMS: "There are no items in your cart" - READMORE: "View \"%s\"" - REMOVEALL: "remove from cart" + CannotPurchase: 'This {Title} cannot be purchased.' + Checkout: Checkout + Cleared: 'Cart was successfully cleared.' + ContinueShopping: 'Continue Shopping' + Headline: 'Shopping cart' + InvalidSecurityToken: 'Invalid security token, possible CSRF attack.' + ItemAdded: 'Item has been added successfully.' + ItemNotFound: 'Item not found.' + ItemRemoved: 'Item has been successfully removed.' + ItemsInCartPlural: 'There are {quantity} items in your cart.' + ItemsInCartSingular: 'There is 1 item in your cart.' + NoCartFound: 'No cart found.' + NoCartInitialised: 'no cart initialised' + NoItems: 'There are no items in your cart.' + NoOrder: 'No current order.' + ProceedToCheckout: 'Proceed to Checkout' + ProductNotFound: 'Product not found.' + QuantitySet: 'Quantity has been set.' + RemoveAllTitle: 'Remove all of "{Title}" from your cart' + RemoveTitle: 'Remove "{Title}" from your cart.' + TableSummary: 'Current contents of your cart.' SimpleShippingModifier: - PLURALNAME: Modifiers - SHIPTO: "Ship to %s" + PLURALNAME: 'Simple Shipping Modifiers' SINGULARNAME: Shipping + ShipToCountry: 'Ship to {Country}' db_Country: Country - SteppedCheckoutPage.ss: - NOITEMS: "There are no items in your cart." SubTotalModifier: - PLURALNAME: "Sub Totals" - SINGULARNAME: "Sub Total" + PLURALNAME: 'Sub Totals' + SINGULARNAME: 'Sub Total' TaxModifier: - ATRATE: "@ %s" + AtRate: '@ {Rate}%' PLURALNAME: Taxes SINGULARNAME: Tax db_Rate: Rate - VariationsTable.ss: - ADD: "Add \"%s\" to your cart" - ADDLINK: "Add this item to cart" - QUANTITYCART: "Quantity in cart" + TaxReport: + Description: 'Report tax charged on orders. Only includes orders that have been paid.' + Title: Tax + Variation: + SINGULARNAME: Variation + VariationForm: + ChooseAttribute: 'Choose {attribute} …' + ProductNotAvailable: 'This product is not available with the selected options.' + VariationNotAvailable: 'That variation is not available, sorry.' WeightShippingModifier: - PLURALNAME: Modifiers + PLURALNAME: 'Weight Shipping Modifiers' SINGULARNAME: Shipping - TABLETITLE: "Shipping (%f kg)" - WorldpayPayment: - PLURALNAME: "Worldpay Payments" - SINGULARNAME: "Worldpay Payment" + TableTitle: 'Shipping ({Kilograms} kg)' Zone: PLURALNAME: Zones SINGULARNAME: Zone db_Description: Description db_Name: Name has_many_Regions: Regions - ZoneAdmin: - MENUTITLE: Zones ZoneRegion: - PLURALNAME: "Zone Regions" - SINGULARNAME: "Zone Region" + PLURALNAME: 'Zone Regions' + SINGULARNAME: 'Zone Region' has_one_Zone: Zone - shop: - Checkout: "Payment Type" - STATUS: Status - CartEditField: - REMOVE: "Remove" - VARIATION: "Variation" - CartForm: - UPDATE_CART: "Update Cart" - REMOVED_ITEMS: "Removed {count} items." - UPDATED_ITEMS: "Updated {count} items." - OnsitePaymentCheckoutComponent: - CREDIT_CARD_INVALID: "Credit card is invalid" - PaymentCheckoutComponent: - NO_PAYMENT_METHOD: "Payment method not provided" - UNSUPPORTED_GATEWAY: "Gateway not supported" - CheckoutStep: - # These should probably go somewhere else, as they are pretty generic. - CONTINUE: "Continue" - SHIPPING: "Shipping" - SUMMARY: "Summary" - GRAND_TOTAL: "Grand Total" - CheckoutStep_Address: - SUPPLY_CONTACT_INFO: "Supply your contact information" - SEPARATE_BILLING: "Bill to a different address from this" - ENTER_SHIPPING_ADDRESS: "Please enter your shipping address details." - BILL_TO: "Bill To:" - SHIP_TO: "Ship To:" - CheckoutStep_Membership: - CONTINUE_AS_GUEST: "Continue as Guest" - # This is an option presented to the user - CREATE_ACCOUNT: "Create an Account" - # This is an action (Button label) - CREATE_NEW_ACCOUNT: 'Create new Account' - VariationForm: - CHOOSE_ATTRIBUTE: "Choose {attribute} …" - VARIATION_NOT_AVAILABLE: "That variation is not available, sorry." - PRODUCT_NOT_AVAILABLE: "This product is not available with the selected options." - PriceTag: - # Save as in savings (you saved this much from the original price) - SAVE: "Save" diff --git a/lang/fr.yml b/lang/fr.yml index ed29fe217..6ba5d2527 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -1,189 +1,492 @@ ---- fr: + OrdersAdmin: + MENUTITLE: Commandes + ProductCatalogAdmin: + MENUTITLE: Catalogue + ZoneAdmin: + MENUTITLE: Zones + AbandonedCartReport: + Description: 'Aperçu des paniers abandonnés pendant une période donnée. Grouper les résultats par année, mois, jour.' + Title: 'Paniers abandonnés' AccountPage: - Message: "Vous devez avoir un login avant d'avoir accès à votre page de profile. Si vous n'êtes pas enregistré, vous ne pourrez avoir accès qu'à partir du moment ou vous ferez votre première commande, sinon veuillez écrire vos renseignements ci-dessous." - NOPAGE: "Aucun compte sur ce site - créez-en un s'il vous plaît!" - AccountPage.ss: - COMPLETED: "Commandes terminées" - HISTORY: "Historique de vos commandes" - INCOMPLETE: "Commandes incomplètes" - NOCOMPLETED: "Aucune commande n'a été trouvée." - NOINCOMPLETE: "Aucune commande incomplète n'a été trouvée" - ORDER: "Commande #" - READMORE: "En savoir plus sur la commande #%s" - AccountPage_order.ss: - ADDRESS: Adresse - AMOUNT: Somme - BACKTOCHECKOUT: "Cliquer ici pour vous rendre à la page de la caisse" - CITY: Ville - COUNTRY: Pays - DATE: Date - DETAILS: Détails - EMAILDETAILS: "Une copie confirmant les détails a été envoyé vers votre adresse email." - NAME: Nom - PAYMENTMETHOD: Methode - PAYMENTSTATUS: "Statut du payement" - Cart.ss: - ADDONE: "Ajouter un ou plusieurs \"%s\" à votre panier" - CheckoutClick: "Cliquez ici pour passer au paiement" - CheckoutGoTo: "Passer au paiement" - HEADLINE: "Mon Panier" - NOITEMS: "Il ya aucun article dans votre panier" - PRICE: Prix - READMORE: "Cliquez ici pour en savoir plus sur \"%s\"" - REMOVE: "Enlève \"%s\" de votre commande" - REMOVEALL: "Supprimer tous les \"%s\" de votre panier" - REMOVEONE: "Supprimer un des \"%s\" de votre panier" - SUBTOTAL: Sous-Total - TOTAL: Total + AddressBook: 'Carnet d''adresses' + DESCRIPTION: 'Ici, l''utilisateur peut consulter les données de son compte ainsi qu''un aperçu des commandes passées. ' + DefaultTitle: Compte + EditProfile: 'Éditer le profil' + LogOut: 'Déconnexion' + Login: "Vous devez vous connecter avant de pouvoir accéder à votre compte. Si vous n'êtes pas enregistré, vous pourrez y accéder seulement avant d'avoir passé une première commande. Si vous possédez déjà un compte, entrez vos données ci-dessous." + LoginAgain: 'Vous avez été déconnecté. Si vous souhaitez vous reconnecter, veuillez le faire ci-dessous.' + MemberEmail: Courriel + MemberLastVisit: 'Dernière visite' + MemberName: Nom + MemberSince: 'Membre depuis' + NoPage: 'Aucune AccountPage n''a été trouvée. Veuillez en créer une dans le CMS!' + NoPastOrders: 'Pas de commandes passées trouvées.' + NumberOfOrders: 'Nombre de commandes' + PLURALNAME: 'Pages du compte' + PastOrders: 'Commandes passées' + SINGULARNAME: 'Page du compte' + Title: 'Mon compte' + AccountPage_AddressBook: + CreateNewTitle: 'Créer une nouvelle adresse' + NoAddress: 'Pas d''adresses trouvées' + Title: 'Adresse par défaut' + DefaultShippingAddress: 'Adresse de livraison par défaut' + DefaultBillingAddress: 'Adresse de facturation par défaut' + MakeDefaultShipping: 'Définir comme adresse de livraison par défaut' + MakeDefaultShippingTitle: 'Définir cette adresse comme adresse de livraison par défaut' + MakeDefaultBilling: 'Définir comme adresse de paiement par défaut' + MakeDefaultBillingTitle: 'Définir cette adresse comme adresse de paiement par défaut' + DeleteAddress: 'Effacer cette adresse' + AccountPage_EditProfile: + Title: 'Éditer le profil' + Address: + AddressHint: 'Rue et numéro' + AddressLine2Hint: 'locaux, bâtiment, appartement, unité, étage, etc.' + BillingAddress: 'Adresse de facturation' + CityHint: 'Ville, département, division administrative, district' + CreateNewAddress: 'Créer une nouvelle adresse' + ExistingBillingAddress: 'Adresse de facturation existante' + ExistingShippingAddress: 'Adresse de livraison existante' + PLURALNAME: Adresses + SINGULARNAME: Adresse + SaveDefaults: 'Enregistrer comme réglages par défaut' + SaveNew: 'Enregistrer la nouvelle adresse' + ShippingAddress: 'Adresse de livraison' + StateHint: 'ou province, territoire, canton, île' + db_Address: Adresse + db_AddressLine2: 'Complément d''adresse (optionnel)' + db_City: Ville + db_Company: Companie + db_Country: Pays + db_FirstName: 'Prénom' + db_Phone: 'Numéro de téléphone' + db_PostalCode: 'Numéro postal' + db_State: État + db_Surname: Nom + has_one_Member: Membre + CartForm: + REMOVED_ITEMS: '{count} articles supprimés.' + UPDATED_ITEMS: '{count} articles mis à jour' + UpdateCart: 'Mettre à jour le panier' + CartPage: + DESCRIPTION: 'Une page sur laquelle l''utilisateur pourra consulter et modifier le contenu de son panier.' + DefaultTitle: 'Panier' + PLURALNAME: 'Pages du panier' + SINGULARNAME: 'Page du panier' + has_one_CheckoutPage: 'Page de check-out' + has_one_ContinuePage: 'Page continuer' + Checkout: + IdFieldNotFound: 'Champ obligatoire manquant : {IdentifierField}' + MemberExists: 'Un membre avec ces données existe déjà {Field} {Identifier}' + MembershipIsNotAllowed: 'Créer de nouveaux comptes n''est pas permis' + NoPaymentMethod: 'Cette méthode de paiement n''existe pas' + PasswordRequired: 'Un mot de passe est nécessaire' + TermsAndConditionsLink: 'J''accepte les conditions générales formulées sur la page {TermsPageTitle}' + CheckoutComponentValidator: + InvalidDataMessage: 'Des problèmes ont été rencontrées avec les données entrées. Voir ci-dessous' + CheckoutField: + AccountInfo: 'Veuillez choisir un mot de passe, afin de pouvoir vous connecter et vérifier l''état de vos commandes dans le futur' + MemberLoginInfo: 'Si vous êtes déjà membre, veuillez vous connecter' + MembershipDetails: 'Détails du compte' + MustAgreeToTerms: 'Vous devez accepter les conditions générales' + Password: Mot de passe + PaymentType: 'Type de paiement' CheckoutPage: - NOPAGE: "Pas de paiement sur la page de ce site, veuillez en créer un!" - CheckoutPage.ss: - CHECKOUT: Commander - ORDERSTATUS: "Etat de la commande" - PROCESS: Processus - ChequePayment: - MESSAGE: "Paiement par Chèque. Veuillez prendre note que les produits ne seront expédiés qu'après réception de votre paiement." + DESCRIPTION: 'Page gérant le processus de check-out' + DefaultTitle: Check-out + PLURALNAME: 'Pages de check-out' + PaymentErrorMessage: 'Erreur reçue de la passerelle de paiement.' + ProceedToPayment: 'Continuer vers le paiement' + PurchaseCompleteDescription: 'Ce message est inclus dans le courriel de confirmation, une fois le check-out effectué par le client.' + SINGULARNAME: 'Page de check-out' + SubmitPayment: 'Soumettre le paiement' + db_PurchaseComplete: 'Achat terminé' + CheckoutStep: + Continue: Continuer + Shipping: Livraison + Summary: Résumé + CheckoutStep_Address: + BillTo: 'Facturer à :' + EnterShippingAddress: 'Entrer les détails de votre adresse de livraison' + SeperateBilling: 'Facturer à une adresse différente de celle-ci' + ShipTo: 'Envoyer à :' + SupplyContactInformation: 'Entrer vos informations de contact' + CheckoutStep_Membership: + ContinueAsGuest: 'Continuer comme invité' + CreateAccount: 'Créer un compte' + CreateNewAccount: 'Créer un nouveau compte' + CreateAddressForm: + AddressSaved: 'Votre adresse a été enregistrée' + CustomerReport: + Description: 'Rapport des clients' + Title: Clients + FlatTaxModifier: + PLURALNAME: Taxes + SINGULARNAME: Taxe + FreeShippingModifier: + Free: GRATUIT + PLURALNAME: 'Livraison gratuite' + SINGULARNAME: Livraison + GlobalTaxModifier: + Included: '(inclu dans le prix ci-dessus)' + PLURALNAME: Taxes + SINGULARNAME: Taxe + db_Country: Pays MemberForm: - DETAILSSAVED: "Vos coordonnées ont été enregistrées" - LOGGEDIN: "Vous êtes actuellement connecté en tant que" + DetailsSaved: 'Vos données ont été enregistrées' + Save: 'Enregistrer les modifications' + SaveAndProceed: 'Enregistrer et passer à la caisse' + OnsitePaymentCheckoutComponent: + InvalidCreditCard: 'La carte de crédit est invalide' Order: - INCOMPLETE: "Commande incomplète" - SUCCESSFULL: "Commande réussie" - OrderInformation.ss: - ADDRESS: Adresse - AMOUNT: Quantité - BUYERSADDRESS: "Adresse de l'acheteur" - CITY: Ville - COUNTRY: Pays - CUSTOMERDETAILS: "Détails du Client" - DATE: Date - DETAILS: Détails - EMAIL: "Courrier électronique" - MOBILE: GSM - NAME: Nom - ORDERSUMMARY: "Récapitulatif des commandes" - PAYMENTID: "ID du Paiement" - PAYMENTINFORMATION: "Information de Paiement" - PAYMENTMETHOD: Méthode - PAYMENTSTATUS: "Statut du Paiement" - PHONE: Téléphone - PRICE: Prix - PRODUCT: Produit - QUANTITY: Quantité - READMORE: "Cliquez ici pour lire plus de \"%s\"" - SHIPPINGDETAILS: "Détails des achats" - SUBTOTAL: Sous-Total - TOTAL: Total - TOTALOUTSTANDING: "Total encours" - TOTALPRICE: "Prix Total" - OrderInformation_Editable.ss: - ADDONE: "Ajoutez un \"%s\" à votre panier" - NOITEMS: "Il n'y a pas d'article dans votre panier." - ORDERINFORMATION: "Information Pour commander" - PRICE: Prix - PRODUCT: Produit - QUANTITY: Quantité - READMORE: "Cliquez ici pour lire la suite sur \"%s\"" - REMOVE: "Enlève \"%s\" de votre commande" - REMOVEALL: "Supprimer tous les \"%s\" du panier" - REMOVEONE: "Supprimez un \"%s\" de votre panier" - SUBTOTAL: Sous-Total - TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." - TOTAL: Total - TOTALPRICE: "Prix Total" - OrderInformation_NoPricing.ss: - ADDRESS: Adresse - BUYERSADDRESS: "Adresse de l'acheteur" - CITY: Ville - COUNTRY: Pays - CUSTOMERDETAILS: "Détails du Client" - EMAIL: "Courrier électronique" - MOBILE: GSM - NAME: Nom - ORDERINFO: "Information pour Commander #" - PHONE: Téléphone - TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." - OrderInformation_PackingSlip.ss: - DESCRIPTION: Description - ITEM: Article - ORDERDATE: "Date de la commande" - ORDERNUMBER: "Numéro de Commande" - PAGETITLE: "Imprimer la commande" - QUANTITY: Quantité - TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." - OrderInformation_Print.ss: - PAGETITLE: "Imprimez votre commande" - OrderReport: - CHANGESTATUS: "Changement d'état des commandes" - NOTEEMAIL: "Note/Courrier électronique" - SENDNOTETO: "Envoyer cette note à %s (%s)" - Order_Content.ss: - NOITEMS: "Il n'y a aucun article dans votre ordre." - PRICE: Prix - PRODUCT: Produit - QUANTITY: Quantité - READMORE: "Clique ici pour lire plus sur \"%s\"" - SUBTOTAL: Sous-total - TOTAL: Total - TOTALPRICE: "Prix Total" - Order_Member.ss: - ADDRESS: Adresse - CITY: Ville - COUNTRY: Pays - EMAIL: Email - MOBILE: Mobile - NAME: Nom - PHONE: Téléphone - Order_ReceiptEmail.ss: - HEADLINE: "Reçu pour votre commande sur ce magasin" - TITLE: "Réception de la boutique" - Order_StatusEmail.ss: - HEADLINE: "Changement du statut du magasin" - STATUSCHANGE: "Status modifié à \"%s\" pour la commande #" - TITLE: "Changement du statut du magasin" - PaymentInformation.ss: - AMOUNT: Montant - DATE: Date - DETAILS: Détails - PAYMENTID: "ID du Paiement" - PAYMENTINFORMATION: "Information sur le paiement" - PAYMENTMETHOD: Méthode - PAYMENTSTATUS: "Etat du paiement" - TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." - Product.ss: - ADD: "Ajouter \"%s\" dans votre panier" - ADDLINK: "Ajouter un article à votre panier" - ADDONE: "Ajouter un ou plusieurs \"%s\" de votre panier" - AUTHOR: Auteur - FEATURED: "Il s'agit d'une description du produit." - GOTOCHECKOUT: "Aller au paiement maintenant" - GOTOCHECKOUTLINK: "» Aller au paiement" - IMAGE: "%s image" - ItemID: "Article #" - NOIMAGE: "Désolé, pas d'image disponible du produit pour \"%s\"" - QUANTITYCART: "Quantité dans le panier" - REMOVE: "Supprimer \"%s\" de votre panier" - REMOVEALL: "Supprimez un \"%s\" de votre panier" - REMOVELINK: "» Retiré de votre panier" - SIZE: Taille - ProductCategory.ss: - FEATURED: "Produits " - OTHER: "Autres Produits" - ProductCategoryItem.ss: - ADD: "Ajouter un \"%s\" dans votre panier" - ADDLINK: "Ajouter un article au panier" - ADDONE: "Ajouter un ou plusieurs \"%s\" dans votre panier" - AUTHOR: Auteur - GOTOCHECKOUT: "Aller à la caisse maintenant" - GOTOCHECKOUTLINK: "» Aller à la caisse" - IMAGE: "%s image" - NOIMAGE: "Désolé, il n'y a pas d'image pour le produit \"%s\"" - QUANTITYCART: "Quantité dans le panier" - READMORE: "Cliquez ici pour en savoir plus sur \"%s\"" - READMORECONTENT: "CLiquez pour en savoir plus »" - REMOVE: "Supprimer un \"%s\" de votre panier" - REMOVEALL: "Supprimer un des \"%s\" du panier" - REMOVELINK: "» Retiré du panier" - ProductMenu.ss: - GOTOPAGE: "Aller à la page %s" + BillTo: 'Facturer à' + Date: Date + DateFrom: 'Date du' + DateTo: 'Date au' + GrandTotal: 'Total' + Invoice: Reçu + OrderHeadline: 'Commande #{OrderNo} {OrderDate}' + Outstanding: Impayé + OutstandingWithAmount: 'Impayé: {Amount}' + PLURALNAME: Commandes + Print: Imprimer + Quantity: Quantité + SINGULARNAME: Commande + STATUS_ADMINCANCELLED: 'Annulé par admin' + STATUS_CART: Panier + STATUS_COMPLETE: Terminé + STATUS_MEMBERCANCELLED: 'Annulé par le membre' + STATUS_PAID: Payé + STATUS_PROCESSING: En cours de traitement + STATUS_SENT: Envoyé + STATUS_UNPAID: Impayé + ShipTo: 'Envoyer à' + SubTotal: Sous-total + Total: Total + TotalOutstanding: 'Total impayé' + TotalPrice: 'Prix total' + TotalPriceWithCurrency: 'Prix total ({Currency})' + UnitPrice: 'Prix unitaire' + db_Dispatched: Expédié + db_Email: Courriel + db_FirstName: 'Nom' + db_IPAddress: 'Adresse IP' + db_Notes: Message + db_Paid: Payé + db_Placed: Placé + db_Printed: Imprimé + db_ReceiptSent: 'Reçu envoyé' + db_Reference: Référence + db_SeparateBillingAddress: 'Adresse de facturation différente' + db_Status: Statut + db_Surname: Nom + db_Total: Total + has_many_Items: Articles + has_many_Modifiers: Modificateur + has_many_OrderStatusLogs: 'Logs des statuts de commandes' + has_many_Payments: Paiements + has_one_BillingAddress: 'Adresse de facturation' + has_one_Member: Membre + has_one_ShippingAddress: 'Adresse de livraison' + OrderActionsForm: + CancelOrder: 'Annuler cette commande' + MakePayment: 'Effectuer le paiement' + ManualNotAllowed: 'Paiement manuel non autorisé' + PayOrder: 'Payer le solde' + PaymentMethod: 'Méthode de paiement' + OrderAdmin: + ReceiptTitle: '{SiteTitle} Commande {OrderNo}' + OrderAttribute: + PLURALNAME: Caractéristiques + SINGULARNAME: Caractéristique + db_CalculatedTotal: 'Total calculé' + has_one_Order: Commande + OrderForm: + CouldNotProcessPayment: 'Le paiement n''a pas pu être effectué' + OrderCancelled: 'Commande annulée avec succès' + OrderItem: + PLURALNAME: Articles + SINGULARNAME: Article + db_Quantity: Quantité + db_UnitPrice: 'Prix unitaire' + OrderModifier: + PLURALNAME: Modificateur + SINGULARNAME: Paramètre + db_Amount: Quantité + db_Sort: Trier + db_Type: Type + OrderProcessor: + NoItems: 'La commande n''a aucun article' + NoOrder: 'La commande n''existe pas' + NoOrderStarted: 'Une nouvelle commande n''a pas encore été commencée' + NotCart: 'La commande n''est pas un panier' + OrderStatusLog: + PLURALNAME: 'Entrées de journal des statuts de commandes' + SINGULARNAME: 'Entrée du journal des commandes' + db_DispatchTicket: 'Envoyer le ticket' + db_DispatchedBy: 'Envoyé par' + db_DispatchedOn: 'Envoyé le' + db_Note: Note + db_PaymentCode: 'Code de paiement' + db_PaymentOK: 'Paiement OK' + db_SentToCustomer: 'Envoyé au client' + db_Title: Titre + has_one_Author: Auteur + has_one_Order: Commande + Payment: + Note: Note + PaymentsHeadline: Paiement(s) + PaymentCheckoutComponent: + NoPaymentMethod: 'Méthode de paiement non sélectionnée' + UnsupportedGateway: 'Passerelle non supportée' + PaymentProcessor: + CantPay: "La commande ne peut pas être payée" + InvalidGateway: "`{gateway}` n'est pas une passerelle de paiement valide" + PickupShippingModifier: + PLURALNAME: Modificateur + SINGULARNAME: 'Retirer l''envoi' + PriceTag: + SAVE: Enregistrer + Product: + AddToCart: 'Ajouter au panier' + AddToCartTitle: 'Ajouter "{Title}" au panier' + AdditionalCategories: 'Catégories supplémentaires' + AllowPurchase: 'Autoriser le produit pour l''achat' + Category: Catégorie + CategoryDescription: 'Ceci est la page parente ou la catégorie par défaut' + Code: 'Code du produit' + CostPriceDescription: 'Prix de gros avant majoration.' + DESCRIPTION: 'Page du produit dans la boutique' + Featured: 'Produit phare' + Image: 'Image du produit' + ImageAltText: '{Title} image' + InternalItemID: 'Code de produit/SKU' + Model: Modèle + NoImage: 'pas d''image' + PLURALNAME: Produits + PageTitle: 'Titre du produit' + Price: Prix + PriceDesc: 'Prix de vente de base de ce produit' + ProductCodeShort: SKU + SINGULARNAME: Produit + Size: Taille + View: 'Voir le produit' + db_AllowPurchase: 'Autoriser l''achat' + db_BasePrice: Prix + db_CostPrice: 'Prix de revient' + db_Depth: Profondeur + db_Featured: Vedette + db_Height: Hauteur + db_InternalItemID: 'ID interne de l''article' + db_Model: Model + db_Popularity: Popularité + db_Weight: Poids + db_Width: Largeur + has_many_Variations: Variantes + has_one_Image: Image + many_many_ProductCategories: 'Catégories de produits' + many_many_VariationAttributeTypes: 'Types d''attributs de variantes' + ProductAttributeType: + PLURALNAME: Attributs + SINGULARNAME: Attribut + SaveFirstInfo: 'Enregistrez d''abord, vous pourrez ensuite ajouter des valeurs.' + belongs_many_many_Product: Produit + db_Label: Labe + db_Name: Nom + has_many_Values: Valeurs + ProductAttributeValue: + PLURALNAME: Valeurs + SINGULARNAME: Valeur + belongs_many_many_ProductVariation: 'Variante de produit' + db_Sort: Trier + db_Value: Valeur + has_one_Type: Type + ProductCategory: + DESCRIPTION: 'Catégorie de produit dans la boutique' + PLURALNAME: Catégories + SINGULARNAME: Catégorie + belongs_many_many_Products: Produits + ProductGroup: + Next: Suivant + Page: Page + Previous: précédent + ViewNext: 'Voir la page suivante' + ViewPage: 'Voir la page No {pageNum}' + ViewPrevious: 'Voir la page précédente' + ProductMenu: + GotoPageTitle: 'Aller à la page {Title}' + ProductReport: + Description: "Comprendre quels produits sont performants et lesquels ne le sont pas." + Title: Produits + ProductVariation: + MustSaveFirstMessage: 'Vous pourrez choisir des attributs de variantes, s''ils existent, après avoir enregistré pour la première fois.' + NoAttributeValuesMessage: '{attribute} n''a pas de valeurs à choisir. Vous pouvez les créer dans la section "Produits" > "Types d''attributs du produit" du CMS.' + PLURALNAME: Variantes + SINGULARNAME: Variante + db_InternalItemID: 'ID interne de l''article' + db_Price: Prix + db_Version: Version + has_one_Image: Image + has_one_Product: Produit + many_many_AttributeValues: 'Valeurs d''attribut' + ProductVariation_OrderItem: + PLURALNAME: Articles + SINGULARNAME: Article + db_ProductVariationVersion: 'Version de la variante d''article' + has_one_ProductVariation: 'Variante d''article' + ProductVariationsExtension: + Attributes: Caractéristiques + AttributesDescription: 'Ces champs indiquent la manière dont les variantes diffèrent. Une fois sélectionnées, ils peuvent être édités pour chaque variante.' + Variations: Variantes + VariationsInfo: 'Prix - Comme vous avez au moins une variante de produit, le prix peut être choisi sous l''onglet "Variantes".' + Product_OrderItem: + PLURALNAME: Articles + SINGULARNAME: Article + db_ProductVersion: 'Version du produit' + has_one_Product: Produit + RegionRestriction: + PLURALNAME: 'Restrictions régionales' + SINGULARNAME: 'Restriction régionale' + db_City: Ville + db_Country: Pays + db_PostalCode: 'Code postal' + db_State: État + SetLocationForm: + ChooseCountry: 'Choisir un pays...' + Country: Pays + ShippingModifier: + PLURALNAME: Modificateurs + SINGULARNAME: Livraison + Shop: + Change: Change + Customer: Client + Date: Date + DateFormatNice: '%m/%d/%G' + DateTimeFormatNice: '%m/%d/%G %I:%M%p' + DateTimeFormatNice24: 'j/m/A H:i' + DevToolsTitle: 'Rechercher des outils de développement' + Edit: Éditer + Quantity: Quantité + ReadMoreTitle: 'Cliquer ici pour en savoir plus sur "{Title}"' + Remove: Retirer + View: Voir + ShopConfig: + AllowedCountries: 'Pays autorisés pour la commande et l''expédition' + AllowedCountriesTabTitle: 'Pays autorisés' + CustomerGroup: 'Groupe pour ajouter les nouveaux clients à' + DefaultImage: 'Image de produit par défaut' + TermsPage: 'Page des conditions générales' + ShopCurrency: + Free: 'GRATUIT' + ShopDashboard: + NumberOfAccounts: 'Nombre de comptes à afficher' + NumberOfDays: 'Nombre de jours à afficher' + NumberOfOrders: 'Nombre de commandes à afficher' + RecentAccounts: 'Comptes récents' + RecentAccountsDescription: 'Montre les inscriptions récentes' + RecentOrders: 'Commandes récentes' + RecentOrdersChart: 'Historique des commandes' + RecentOrdersChartDescription: 'Affiche les commandes récentes sur un graphique' + RecentOrdersDescription: 'Affiche les commandes récentes' + ShopDevelopmentAdmin: + BuildTasks: 'Construit des tâches' + CartCleanup: 'Effacer les anciens paniers' + CartCleanupDesc: 'Supprimer les paniers abandonnés' + CartTasks: 'Paniers' + ClearCart: 'Vider le panier actuel' + DebugCart: 'Déboguer le panier d''achat' + DeleteOrders: 'Effacer toutes les commandes' + DeleteOrdersDesc: 'Supprimer toutes les commandes, modificateurs et paiements de la base de données.' + DeleteProducts: 'Supprimer tous les produits' + DeleteProductsDesc: 'Supprimer tous les produits de la base de données' + PopulateCart: 'Remplir le panier avec quelques articles disponibles' + PopulateShop: 'Alimenter le shop' + PopulateShopDesc: 'Alimenter la boutique avec des produits, catégories et pages nécessaires fictifs.' + RecalculateOrders: 'Recalculer toutes les commandes' + RecalculateOrdersDesc: 'Recalculer toutes les valeurs des commandes. Attention: ceci va écraser l''historique de ces valeurs.' + RunAllTests: 'Exécuter toutes les unités de test de la boutique' + UnitTests: 'Unité de test' + ShopEmail: + AdminNotificationSubject: 'Notification de commande #{OrderNo}' + AdminNotificationTitle: 'Reçu' + CancelSubject: 'Commande #{OrderNo} annulée par le membre' + ConfirmationSubject: 'Confirmation de commande #{OrderNo}' + ConfirmationTitle: 'Confirmation de commande' + ReceiptSubject: 'Reçu pour la commande #{OrderNo} ' + ReceiptTitle: 'Reçu' + StatusChangeTitle: 'Changement de statuts de la boutique' + StatusChanged: 'Statut de la commander #{OrderNo} changé en "{OrderStatus}"' + ShopPeriodReport: + Description: 'Rapport périodique de la boutique' + Title: 'Rapport périodique de la boutique' + ShopQuantityField: + AddOne: 'Ajouter une pièce ou plus de "{ItemTitle}" à votre panier' + RemoveOne: 'Supprimer une pièce ou plus de "{ItemTitle}" de votre panier' + ShopSalesReport: + Description: 'Monitorer les performances de vente pour une période donnée. Grouper les résultats par année, mois ou jour.' + Title: 'Ventes' + ShopSideReport: + AllProducts: 'Tous les produits' + FeaturedProducts: 'Produits vedettes' + Heavy: 'Produits lourds' + NoImage: 'Produits sans image' + ShopGroup: Boutique + ShoppingCart: + CannotPurchase: 'Ce/cette {Title} ne peut pas être acheté' + Checkout: Check-out + Cleared: 'Le panier a été vidé avec succès.' + ContinueShopping: 'Continuer les achats' + Headline: 'Panier d''achats' + InvalidSecurityToken: 'Jeton de sécurité invalide. Attaque CSRF possible.' + ItemAdded: 'L''article a bien été ajouté.' + ItemNotFound: 'Article non trouvé.' + ItemRemoved: 'L''article a été supprimé avec succès.' + ItemsInCartPlural: 'Il y a {quantity} articles dans votre panier' + ItemsInCartSingular: 'Il y a 1 article dans votre panier.' + NoCartFound: 'Pas de panier trouvé.' + NoCartInitialised: 'Pas de panier initialisé' + NoItems: 'Il n''y a aucun article dans votre panier.' + NoOrder: 'Pas de commandes en cours.' + ProceedToCheckout: 'Passer à la caisse' + ProductNotFound: 'Aucun produit n''a été trouvé. ' + QuantitySet: 'La quantité a été fixée.' + RemoveAllTitle: 'Supprimer tous les "{Title}" de votre panier.' + RemoveTitle: 'Supprimer "{Title}" de votre panier.' + TableSummary: 'Contenu de votre panier.' + SimpleShippingModifier: + PLURALNAME: 'Modificateurs de frais de livraison' + SINGULARNAME: Expédition + ShipToCountry: 'Expédier vers {Country}' + db_Country: Pays + SubTotalModifier: + PLURALNAME: 'Sous-totaux' + SINGULARNAME: 'Sous-total' + TaxModifier: + AtRate: 'taux' + PLURALNAME: Taxes + SINGULARNAME: Taxe + db_Rate: Taux + TaxReport: + Description: 'Rapport des taxes appliquées aux commandes. Inclus seulement les commandes ayant été payées.' + Title: Taxe + Variation: + SINGULARNAME: Variante + VariationForm: + ChooseAttribute: 'Choisir {attribute} ...' + ProductNotAvailable: 'Cet article n''est pas disponible avec les options sélectionnées.' + VariationNotAvailable: 'Cette variante n''est pas disponible, désolé.' + WeightShippingModifier: + PLURALNAME: 'Frais de livraison selon le poids' + SINGULARNAME: Livraison + TableTitle: 'Livraison ({Kilograms} kg)' + Zone: + PLURALNAME: Zones + SINGULARNAME: Zone + db_Description: Description + db_Name: Nom + has_many_Regions: Régions + ZoneRegion: + PLURALNAME: 'Zones régionales' + SINGULARNAME: 'Zone régionale' + has_one_Zone: Zone diff --git a/lang/ar.yml b/lang/legacy/ar.yml similarity index 100% rename from lang/ar.yml rename to lang/legacy/ar.yml diff --git a/lang/bg.yml b/lang/legacy/bg.yml similarity index 100% rename from lang/bg.yml rename to lang/legacy/bg.yml diff --git a/lang/da.yml b/lang/legacy/da.yml similarity index 100% rename from lang/da.yml rename to lang/legacy/da.yml diff --git a/lang/legacy/de.yml b/lang/legacy/de.yml new file mode 100644 index 000000000..4472a6a9d --- /dev/null +++ b/lang/legacy/de.yml @@ -0,0 +1,654 @@ +--- +de: + AccountNavigation: + AddressBook: Adressbuch + EditProfile: "Nutzerprofil bearbeiten" + LogOut: Abmelden + MemberEmail: E-Mail + MemberLastVisit: "Letzte Anmeldung" + MemberName: Name + MemberSince: "Kunde seit" + NumberOfOrders: "Anzahl der Bestellungen" + PastOrders: "Frühere Bestellungen" + Title: "Mein Kundenkonto" + AccountPage: + DESCRIPTION: "Kundenkonto-Seite" + LOGIN: "Sie müssen sich einloggen, um auf Ihr Konto zugreifen zu können. Wenn Sie nicht als Kunde registriert sind, können Sie sich hier nicht anmelden bis Sie Ihre erste Bestellung getätigt haben." + LOGINAGAIN: "Sie sind abgemeldet. Wen Sie sich wieder anmelden möchten, können Sie es weiter unten tun." + Message: "Sie müssen sich einloggen um auf Ihr Konto zugreifen zu können. Falls Sie nicht registriert sind, können Sie erst nach Ihrer ersten Bestellung auf Ihr Konto zugreifen. Fall Sie bereits registriert sind, geben Sie folgend Ihre Daten ein." + NOPAGE: "Keine Kundenkonto-Seite auf dieser Website - erstellen Sie bitte eine!" + NoPastOrders: "Keine früheren Bestellungen gefunden." + PLURALNAME: Kundenkontos + SINGULARNAME: Kundenkonto + Title: "Frühere Bestellungen" + NO_PAGE: "Es wurde keine Kundenkonto-Seite gefunden. Bitte erstellen Sie eine im CMS!" + AccountPage.ss: + COMPLETED: "Abgeschlossene Bestellungen" + HISTORY: "Ihr Bestellhistorie" + INCOMPLETE: "Offene Bestellungen" + NOCOMPLETED: "Es konnten keine abgeschlossenen Bestellungen gefunden werden." + NOINCOMPLETE: "Es konnten keine offenen Bestellungen gefunden werden." + ORDER: Bestellung + READMORE: "Zur Detail-Ansicht der Bestellung #%s" + AccountPage_AddressBook: + CreateNewTitle: "Neue Adresse eingeben" + NoAddress: "Keine Adresse gefunden." + Title: Standardadresse + AccountPage_EditProfile: + Title: "Nutzerprofil bearbeiten" + AccountPage_order.ss: + ADDRESS: Adresse + AMOUNT: Menge + BACKTOCHECKOUT: "Klicken Sie hier um zur Kasse zu gelangen" + CITY: Stadt + COUNTRY: Land + DATE: Datum + DETAILS: Details + EMAILDETAILS: "Zur Bestätigung Ihrer Bestellung wurde eine Kopie an Ihre E-Mail Adresse geschickt." + NAME: Name + PAYMENTMETHOD: Bezahlmethode + PAYMENTSTATUS: "Status der Zahlung" + AddProductForm: + ADDTOCART: "In den Warenkorb" + Quantity: Menge + Address: + ADDRESS: Adresse + ADDRESS2HINT: "Gebäude, Wohnung, Etage, Zimmer,..." + ADDRESSHINT: "Straße und Nr." + ADDRESSLINE2: " " + BillingAddress: Rechnungsadresse + CITY: Stadt + CITYHINT: "" + COUNTRY: Land + PHONE: Telefonnummer + PLURALNAME: Adressen + POSTALCODE: Postleitzahl + SINGULARNAME: Adresse + STATE: "Staat" + STATEHINT: "…oder Provinz, Gemeinde, Kanton, Insel" + SaveDefaults: "Standards speichern" + SaveNew: "Neue Adresse speichern" + ShippingAddress: Lieferadresse + db_Address: Adresse + db_AddressLine2: " " + db_City: Stadt + db_Company: Firma + db_Country: Land + db_FirstName: Vorname + db_Latitude: Breitengrad + db_Longitude: Längengrad + db_Phone: Telefonnummer + db_PostalCode: Postleitzahl + db_State: "Staat" + db_Surname: Nachname + has_one_Member: Kunde + AddressBookCheckoutComponent: + CREATENEWADDRESS: "Adresse ändern" + EXISTING_BILLING_ADDRESS: "Vorhandene Rechnungsadresse" + EXISTING_SHIPPING_ADDRESS: "Vorhandene Lieferadresse" + EXISTING_ADDRESS: "Vorhandene Adresse" + Cart.ss: + ADDONE: "Eines oder mehr von \"%s\" zum Warenkorb hinzufügen" + CheckoutClick: "Hier klicken um zur Kasse zu gelangen" + CheckoutGoTo: "Zur Kasse" + HEADLINE: "Mein Warenkorb" + NOITEMS: "In Ihrem Warenkorb befinden sich zur Zeit keine Artikel" + PRICE: Preis + PRODUCT: Produkt + QUANTITY: Menge + READMORE: "Erfahren Sie hier mehr über \"%s\"" + REMOVE: "\"%s\" aus dem Warenkorb entfernen" + REMOVEALL: "Alle \"%s\" aus dem Warenkorb entfernen" + REMOVEONE: "Entfernen Sie eines von \"%s\" aus Ihrem Warenkorb" + SUBTOTAL: Zwischensumme + TABLESUMMARY: "Im Warenkorb befindliche Produkte" + TOTAL: Gesamt + TOTALPRICE: Gesamtpreis + UNITPRICE: Einzelpreis + CartPage: + DESCRIPTION: "Warenkorb-Seite" + PLURALNAME: Warenkorb + SINGULARNAME: Warenkorb + TITLE: Warenkorb + has_one_CheckoutPage: Kassenseite + has_one_ContinuePage: Weiterleitungsseite + CartPage.ss: + CARTEMPTY: "Ihr Warenkorb ist leer." + CONTINUE: "Den Einkauf fortsetzen" + PROCEEDTOCHECKOUT: "Zur Kasse" + Checkout: + IDFIELDNOTFOUND: "Pflichtfeld nicht gefunden: %s" + MEMBEREXISTS: "Es existiert bereits ein Kundenkonto mit der %s %s" + MEMBERSHIPSNOTALLOWED: "Das Erstellen von neuen Kundenkonten ist nicht erlaubt, bitte kontaktieren Sie uns telefonisch oder per E-Mail" + NOPAYMENTMETHOD: "Die Zahlungsart existiert nicht" + PASSWORDREQUIRED: "Passwort ist erforderlich" + CheckoutComponentValidator: + INVALIDMESSAGE: "Es gibt Probleme mit den von Ihnen eingegebenen Daten. Siehe unten:" + CheckoutField: + ACCOUNTINFO: "Bitte wählen Sie ein Passwort, damit Sie sich zukünftig im Kundenkonto einloggen können." + EMAIL: E-Mail + FIRSTNAME: Vorname + MEMBERINFO: "Wenn Sie schon Kunde bei uns sind, klicken Sie bitte hier um sich anzumelden." + MEMBERSHIPDETAILS: Kundeninformationen + MUSTAGREETOTERMS: "Bitte bestätigen Sie, dass sie die AGB gelesen haben" + NOTES: Bemerkungen + PASSWORD: Passwort + SURNAME: Nachname + TERMSANDCONDITIONS: "Ich habe die Allgemeinen Geschäftsbedingungen gelesen und akzeptiert." + PAYMENT_TYPE: Zahlungsart + CheckoutFields: + PAYMENTTYPE: Zahlungsart + CheckoutForm: + PROCEED: "Weiter zur Zahlung" + CheckoutPage: + DESCRIPTION: "Kassenseite" + NOPAGE: "Auf dieser Site existiert keine Kassenseite - bitte erstellen Sie eine neue Seite!" + PLURALNAME: Kassenseiten + PaymentErrorMessage: "Fehler von Payment-Gateway empfangen:" + SINGULARNAME: Kassenseite + TITLE: Kasse + db_PurchaseComplete: "Bestellung abgeschlossen" + PURCHASE_COMPLETE_DESCRIPTION: "Diese Meldung wird dem Kunden im Bestätigungs-E-Mail gesendet, nachdem der Bestellvorgang abgeschlossen wurde." + SUBMIT_PAYMENT: "Bezahlen" + CheckoutPage.ss: + CARTEMPTY: "Ihr Warenkorb ist leer." + CHECKOUT: Kasse + ORDERSTATUS: Bestellstatus + PROCESS: Prozess + ChequePayment: + MESSAGE: "Bezahlung per Scheck (Vorkasse). Bitte beachten: Der Versand der Produkte erfolgt erst nach Zahlungseingang." + CreateAddressForm: + SAVED: "Ihre Adresse wurde gespeichert" + FlatTaxModifier: + PLURALNAME: Steuern + SINGULARNAME: MwSt. + FreeShippingModifier: + FREE: Kostenlos + PLURALNAME: Modifikatoren + SINGULARNAME: Versand + General: + DATEFORMATNICE: "%d.%m.%Y" + DATETIMEFORMATNICE: "%d.%m.%Y %H:%M" + DATETIMEFORMATNICE24: "d.m.Y H:i" + GlobalTaxModifier: + INCLUDED: " (im Preis enthalten)" + PLURALNAME: Steuern + SINGULARNAME: MwSt. + db_Country: Land + MemberForm: + DETAILSSAVED: "Ihre Angaben wurden gespeichert" + LOGGEDIN: "Sie sind angemeldet als " + SAVE: "Änderungen speichern" + SAVEANDPROCEED: "Speichern und weiter zur Kasse" + ORDER: + INVOICE: Rechnung + Order: + CANCELSUBJECT: "Die Bestellung Nummer #%d wurde vom Kunden storniert." + INCOMPLETE: "Bestellung unvollständig" + PLURALNAME: Bestellungen + PRINT: Drucken + SINGULARNAME: Bestellung + SUCCESSFULL: "Bestellung Erfolgreich" + db_AnalyticsSubmitted: "Analytics eingereicht" + db_Dispatched: "Wird ausgelöst" + db_Email: Email + db_FirstName: Vorname + db_Gesamt: Zwischensumme + db_IPAddress: IP-Adresse + db_Paid: Bezahlt + db_Placed: Platziert + db_Printed: Gedruckt + db_ReceiptSent: "Bestätigung gesendet" + db_Reference: Bestellnummer + db_SeparateBillingAddress: "Abweichende Lieferadresse" + db_ShippingGesamt: "Gesamtkosten für den Versand" + db_Status: Status + db_Surname: Nachname + has_many_Items: Artikel + has_many_Modifikatoren: Modifikatoren + has_many_OrderStatusLogs: "Bestell-Status Logs" + has_many_Payments: Zahlungen + has_one_BillingAddress: Rechnungsadresse + has_one_Member: Kunde + has_one_ShippingAddress: Lieferadresse + has_one_ShippingZahlungsart: Versandart + Order.ss: + ORDERNOTES: Bemerkungen + TOTALOUTSTANDING: "Gesamt ausstehend" + OrderActionsForm: + CANCELORDER: "Diese Bestellung stornieren" + MAKEPAYMENT: Zahlen + OUTSTANDING: "Ausstehend: %s" + PAYMENTMETHOD: Zahlungsart + PAYORDER: "Restbetrag bezahlen" + MANUAL_NOT_ALLOWED: "Bezahlung gegen Rechnung nicht erlaubt." + OrderAdmin_Addresses.ss: + ADDRESS: Adresse + BILLTO: "Rechnung an" + SHIPTO: "Liefern an" + OrderAdmin_Content.ss: + ITEMS: Artikel + PRODUCT: Produkt + QUANTITY: Menge + TOTALPRICE: Gesamtpreis + UNITPRICE: Stückpreis + OrderAdmin_Content_ItemLine.ss: + READMORE: "Zeige \"%s\"" + OrderAdmin_Content_SubGesamts.ss: + SUBTOTAL: Zwischensumme + TOTAL: Gesamt + TOTALOUTSTANDING: Ausstehend + OrderAdmin_Customer.ss: + CUSTOMER: Kunde + NOTES: Bemerkungen + OrderAdmin_Printable.ss: + ORDER: Bestellung + OrderAttribute: + PLURALNAME: Attribute + SINGULARNAME: Attribut + db_CalculatedGesamt: Gesamtbetrag + has_one_Order: Bestellung + OrderForm: + COULDNOTPROCESSPAYMENT: "Die Zahlung konnte nicht verarbeitet werden." + LogIn: Anmelden + ORDERCANCELLED: "Bestellung erfolgreich storniert" + OrderHistory: + OrderDatum: Datum + OrderGesamt: Zwischensumme + OrderItems: Produkte + OrderReference: Bestellnummer + OrderStatus: Status + ViewOrder: anzeigen + OrderItem: + PLURALNAME: Produkte + SINGULARNAME: Produkt + db_Quantity: Menge + db_UnitPrice: Stückpreis + OrderModifier: + PLURALNAME: Modifikatoren + SINGULARNAME: Modifikator + db_Betrag: Betrag + db_Sort: Sortieren + db_Type: Art + OrderNotifier: + ADMINNOTIFICATIONSUBJECT: "Bestellbenachrichtigung #%d" + CONFIRMATIONSUBJECT: "Bestellbestätigung #%d" + RECEIPTSUBJECT: "Bestellungseingang #%d" + OrderProcessor: + NULL: "Eine neue Bestellung wurde noch nicht begonnen" + NOITEMS: "Die Bestellung beinhaltet keine Artikel." + NOTCART: "Die Bestellung beinhaltet keinen Warenkorb." + OrderStatusLog: + PLURALNAME: "Bestell Status Log Einträge" + SINGULARNAME: "Bestell Log Einträge" + db_DispatchTicket: "Versand Ticket" + db_DispatchedBy: "Versand von" + db_DispatchedOn: "Versand mit" + db_PaymentCode: "Zahlungs ID" + db_PaymentOK: "Zahlung OK" + db_SentToCustomer: "An Kunden verschickt" + db_Title: Titel + has_one_Author: Autor + has_one_Order: Bestellung + Order_Address.ss: + BILLTO: "Rechnung an" + SHIPTO: "Lieferung an" + Order_AdminNotificationEmail.ss: + TITLE: Shop-Eingang + Order_ConfirmationEmail.ss: + TITLE: Bestellbestätigung + Order_Content.ss: + NOITEMS: "Ihre Bestellung weist keine Artikel auf" + PRICE: Preis + PRODUCT: Produkt + QUANTITY: Menge + READMORE: "Wenn Sie mehr über \"%s\" erfahren möchten, klicken Sie hier" + SUBTOTAL: Zwischensumme + TOTAL: Gesamt + TOTALOUTSTANDING: "Gesamt ausstehend" + TOTALPRICE: Gesamtpreis + UNITPRICE: Stückpreis + Order_Content_ItemLine.ss: + READMORE: "Zeige \"%s\"" + Order_Content_SubGesamts.ss: + SUBTOTAL: Zwischensumme + TOTAL: Gesamt + Order_ItemLine.ss: + READMORE: "Zeige \"%s\"" + Order_Member.ss: + ADDRESS: Adresse + CITY: Stadt + COUNTRY: Land + EMAIL: E-Mail + MOBILE: Handy + NAME: Name + PHONE: Telefon + Order_Payments.ss: + AMOUNT: Betrag + DATE: Datum + PAYMENTMETHOD: Zahlungsart + PAYMENTNOTE: Bemerkungen + PAYMENTSTATUS: Zahlungsstatus + Order_ReceiptEmail.ss: + HEADLINE: Auftragsbestätigung + TITLE: Shop-Eingang + Order_StatusEmail.ss: + HEADLINE: "Shop Status geändert" + STATUSCHANGE: "Status geändert zu \"%s\" für Bestellung Nummer #" + TITLE: "Shop Status geändert" + OrdersAdmin: + MENUTITLE: Bestellungen + Payment: ~ + PaymentProcessor: + INVALID_GATEWAY: "`{gateway}` ist kein gültiges Bezahl-Gateway." + CANTPAY: "Diese Bestellung kann nicht bezahlt werden." + PickupShippingModifier: + PLURALNAME: Modifikatoren + SINGULARNAME: Selbstabholung + Product: + ADDITIONALCATEGORIES: "Zusätzliche Produktkategorien" + ALLOWPURCHASE: "Zulassen, dass dieses Produkt bestellt werden darf" + CATEGORY: Category + CATEGORYDESCRIPTION: "Dies ist die übergeordnete Seite oder Standardkategorie." + CODE: Artikelnummer + CODE_SHORT: "Art. Nr." + COSTPRICE: Kostenpreis + COSTPRICEDESC: "Großhandelspreis ohne Preisaufschlag." + DEPTH: "Tiefe. (%s)" + DESCRIPTION: "Generic content page" + FEATURED: "Hervorgehobenes Produkt" + HEIGHT: "Height (%s)" + IMAGE: Produktabbildung + MODEL: "Die wichtigsten Artikelmerkmale" + PAGETITLE: Produktname + PLURALNAME: Produkte + PRICE: Price + PRICEDESC: Basisverkaufspreis. + SINGULARNAME: Produkt + WEIGHT: "Gewicht (%s)" + WIDTH: "Breite (%s)" + NO_IMAGE: "kein Bild" + db_AllowPurchase: "Kaufen erlauben" + db_BasePrice: Basispreis + db_CostPrice: Kostenpreis + db_Height: Höhe + db_Hervorgehoben: Hervorgehoben + db_InternalItemID: "Interne Produkt ID" + db_Popularity: Popularität + db_Tiefe.: Tiefe. + db_Weight: Gewicht + db_Width: Breite + has_many_Variations: Variationen + has_one_Image: Bild + many_many_ProductCategories: Produktkategorien + many_many_VariationAttributeTypes: Variationsattributtypen + Product.ss: + ADD: "\"%s\" zum Warenkorb hinzufügen" + ADDLINK: "Diesen Artikel zum Warenkorb hinzufügen" + ADDONE: "\"%s\" zum Warenkorb hinzufügen" + AUTHOR: Autor + CODE: Artikelnummer + FEATURED: "Wir empfehlen diesen Artikel." + GOTOCHECKOUT: "Jetzt zur Kasse gehen" + GOTOCHECKOUTLINK: "» Zur Kasse" + IMAGE: "%s Bild" + ItemID: "Artikel Nr." + MODEL: "Die wichtigsten Artikelmerkmale" + NOIMAGE: "Keine Produktabbildung vorhanden für \"%s\"" + QUANTITYCART: "Menge im Warenkorb" + REMOVE: "\"%s\" vom Warenkorb entfernen" + REMOVEALL: "\"%s\" vom Warenkorb entfernen" + REMOVELINK: "» Aus dem Warenkorb entfernen" + SIZE: Größe + ProductAttributeType: + PLURALNAME: Eigenschaften + SINGULARNAME: Eigenschaft + SAVE_FIRST_MESSAGE: "Werte können nach erstmaligem Speichern erfasst werden." + belongs_many_many_Product: Produkt + db_Label: Label + db_Name: Name + has_many_Values: Daten + ProductAttributeValue: + PLURALNAME: Daten + SINGULARNAME: Daten + belongs_many_many_ProductVariation: "Produkt Variationen" + db_Sort: Sortieren + db_Value: Daten + has_one_Type: Art + ProductCatalogAdmin: + MENUTITLE: Katalog + ProductCategory: + DESCRIPTION: "Generic content page" + PLURALNAME: Kategorien + SINGULARNAME: Kategorie + belongs_many_many_Products: Produkte + ProductCategory.ss: + FEATURED: "Empfohlene Artikel" + OTHER: "Andere Produkte" + ProductCategoryItem.ss: + ADD: "\"%s\" zum Warenkorb hinzufügen" + ADDLINK: "Diesen Artikel zum Warenkorb hinzufügen" + ADDONE: "\"%s\" zum Warenkorb hinzufügen" + AUTHOR: Autor + GOTOCHECKOUT: "Zur Kasse" + GOTOCHECKOUTLINK: "» Zur Kasse" + IMAGE: "%s Bild" + NOIMAGE: "Keine Produktabbildung vorhanden für \"%s\"" + QUANTITYCART: "Menge im Warenkorb" + READMORE: "Erfahren Sie hier mehr über \"%s\"" + READMORECONTENT: "mehr »" + REMOVE: "\"%s\" vom Warenkorb entfernen." + REMOVEALL: "1 Einheit von \"%s\" aus dem Warenkorb entferne" + REMOVELINK: "» Aus dem Warenkorb entfernen" + ProductGroup: + NEXT: Nächste + PAGE: Seite + PREVIOUS: Vorherige + VIEW_PREVIOUS: "Zur vorherigen Seite" + VIEW_NEXT: "Zur nächsten Seite" + VIEW_PAGE: "Zeige Seite {pageNum}" + ProductGroupItem.ss: + ADD: "\"%s\" zum Warenkorb hinzufügen" + ADDLINK: "In den Warenkorb" + IMAGE: "%s Bild" + MODEL: "Die wichtigsten Artikelmerkmale" + READMORE: "Erfahren Sie hier mehr über \"%s\"" + VIEW: "Produkt öffnen" + ProductMenu.ss: + GOTOPAGE: "Gehe zur %s Seite" + ProductVariation: + PLURALNAME: Variationen + SINGULARNAME: Variation + db_InternalItemID: "Interne Artikel ID" + db_Price: Preis + db_Version: Version + has_one_Image: Bild + has_one_Product: Produkt + many_many_AttributeValues: Eigenschaften + NO_ATTRIBUTE_VALUES_MESSAGE: "Es gibt keine Werte für {attribute}. Diese können im 'Katalog' erstellt werden." + SAVE_FIRST_MESSAGE: "Attribute können erst nach dem ersten Speichern ausgewählt werden (gesetzt der Fall, sie existieren)." + ProductVariationsExtension: + ATTRIBUTES: "Attribute" + ATTRIBUTES_DESCRIPTION: "Diese Felder definieren die Attribute welche die Variationen ausmachen. Sobald sie ausgewählt wurden, können sie in jeder Variation erfasst werden." + VARIATIONS: "Variationen" + VARIATIONS_INSTRUCTIONS: "Da mehrere Variationen vorhanden sind, muss der Preis bei den Variationen erfasst werden." + ProductVariation_OrderItem: + PLURALNAME: Artikel + SINGULARNAME: Artikel + db_ProductVariationVersion: "Produkt Variations Version" + has_one_ProductVariation: "Produkt Variation" + Product_OrderItem: + PLURALNAME: Artikel + SINGULARNAME: Artikel + db_ProductVersion: "Produkt Version" + has_one_Product: Produkt + RegionRestriction: + PLURALNAME: "Regionale Restriktionen" + SINGULARNAME: "Regionale Restriktion" + db_City: Stadt + db_Country: Land + db_PostalCode: Postleitzahl + db_State: Staat + SetLocationForm: + CHOOSECOUNTRY: "Land auswählen..." + COUNTRY: Land + ShippingModifier: + PLURALNAME: Modifikatoren + SINGULARNAME: Versandkosten + Shop: + DEVTOOLSTITLE: Shop-Entwicklungstools + ShopConfig: + CUSTOMERGROUP: "Gruppe, um neue Kunden zu ergänzen" + DEFAULTIMAGE: "Standard Produktbild" + TERMSPAGE: "Allgemeine Geschäftsbedingungen" + ALLOWED_COUNTRIES: "Länder für Bestellungen und Lieferungen" + ALLOWED_COUNTRIES_TAB_TITLE: "Erlaubte Länder" + ShopCurrency: + FREE: "FREI" + ShopDashboard: + RECENTORDERS: "Letzte Bestellungen" + RECENTORDERSDESCRIPTION: "Letzte Bestellungen anzeigen" + RecentAccounts: "Neue Kundenkonten" + RecentAccountsDescription: "Die letzten Anmeldungen in Kundenkonten anzeigen" + RecentOrdersChart: "Bestellhistorie sortieren" + RecentOrdersChartDescription: "Zeigt die letzten Bestellungen in einem Diagramm" + NUMBER_OF_ACCOUNTS: "Anzahl anzuzeigender Benutzer" + NUMBER_OF_DAYS: "Anzahl anzuzeigender Tage" + NUMBER_OF_ORDERS: "Anzahl anzuzeigender Bestellungen" + # These should probably be moved into a more generic category, since they can be used in a lot of places + EDIT: "Bearbeiten" + CUSTOMER: "Kunde" + DATE: "Datum" + ShopDatabaseAdmin.ss: + BUILDTASKS: "Aufgaben anlegen" + CARTCLEANUP: "Alte Warenkörbe bereinigen" + CARTCLEANUPDESC: "Abgebrochene Warenkörbe entfernen" + CARTTASKS: "Warenkorbaufgaben anzeigen" + RECALCULATEORDERS: "Alle Bestellungen rekalkulieren" + RECALCULATEORDERSDESC: "Neu berechnen aller Bestellwerte. Warnung: Dies wird alle vorhergehenden Werte überschreiben." + RUNALLTESTS: "Alle Shop-Tests starten" + UNITTESTS: "Test der Einheit" + ShopDevelopmentAdmin.ss: + BUILDTASKS: "Aufgaben anlegen" + CARTCLEANUP: "Alte Warenkörbe bereinigen" + CARTCLEANUPDESC: "Abgebrochene Warenkörbe entfernen." + CARTTASKS: "Warenkorbaufgaben anzeigen" + CLEARCART: "Den aktuellen Warenkorb löschen" + DEBUGCART: "Den Warenkorb zurücksetzen" + DELETEORDERS: "Alle Bestellungen löschen" + DELETEORDERSDESC: "Alle Bestellungen, Modifikatoren und Zahlungen aus der Datenbank löschen." + DELETEPRODUCTS: "Delete All Products" + DELETEPRODUCTSDESC: "Alle Produkte aus der Datenbank löschen." + POPULATECART: "Befülle den Warenkorb mit einigen erhältlichen Produkten" + POPULATESHOP: "Befülle den Shop" + POPULATESHOPDESC: "Befülle den Shop mit Dummy-Produkten, Kategorien und erstelle andere notwendige Seiten." + RECALCULATEORDERS: "Alle Bestellungen rekalkulieren" + RECALCULATEORDERSDESC: "Neu berechnen aller Bestellwerte. Warnung: Dies wird alle vorhergehenden Werte überschreiben." + RUNALLTESTS: "Alle Shop-Tests starten" + UNITTESTS: "Test der Einheit" + ShopQuantityField.ss: + ADDONE: "\"%s\" zum Warenkorb hinzufügen" + REMOVEONE: "\"%s\" aus Warenkorb entferrnen" + ShopSideReport: + ALLPRODUCTS: "Alle Produkte" + FEATUREDPRODUCTS: "Hervorgehobene Produkte" + HEAVY: "Schwere Produkte" + NOIMAGE: "Keine Produktabbildung vorhanden" + ShopGROUP: Shop + ShoppingCart: + CANNOTPURCHASE: "Das Produkt %s kann nicht gekauft werden." + CLEARED: "Der Warenkorb wurde erfolgreich gelöscht." + CSRF: "Ungültiger Sicherheitstoken, möglicher CSRF-Angriff." + ITEMADD: "Das Produkt wurde soeben aufgenommen." + ITEMNOTFOUND: "Produkt nicht gefunden." + ITEMREMOVED: "Produkt erfolgreich entfernt." + NOCARTFOUND: "Kein Warenkorb gefunden." + NOCARTINITIALISED: "kein Warenkorb initialisiert" + NOORDER: "Keine aktuelle Bestellung." + PRODUCTNOTFOUND: "Produkt nich gefunden." + QUANTITYSET: "Die Menge wurde eingegeben." + # Pretty generic translation, could be moved to a more generic category + CHANGE: "Wechsel" + ITEMS_IN_CART_PLURAL: "In Ihrem Warenkorb befinden sich {quantity} Artikel." + ITEMS_IN_CART_SINGULAR: "In Ihrem Warenkorb befindet sich 1 Artikel." + CHECKOUT: "Zur Kasse" + SideCart.ss: + HEADLINE: Warenkorb + NOITEMS: "Es befinden sich keine Produkte im Warenkorb" + READMORE: "Zeige \"%s\"" + REMOVEALL: "aus dem Warenkorb löschen" + SimpleShippingModifier: + PLURALNAME: Modifikatoren + SHIPTO: "Senden an %s" + SINGULARNAME: Versandkosten + db_Country: Land + SteppedCheckoutPage.ss: + NOITEMS: "Ihr Warenkorb ist leer." + SubGesamtModifier: + PLURALNAME: Zwischensumme + SINGULARNAME: Zwischensumme + TaxModifier: + ATRATE: "@ %s" + PLURALNAME: Steuern + SINGULARNAME: MwSt. + db_Rate: Rate + VariationsTable.ss: + ADD: "\"%s\" zum Warenkorb hinzufügen" + ADDLINK: "Diese Produkt zum Warenkorb hinzufügen" + QUANTITYCART: "Menge im Warenkorb" + WeightShippingModifier: + PLURALNAME: Modifikatoren + SINGULARNAME: Versandkosten + TABLETITLE: "Versandkosten (%f kg)" + Zone: + PLURALNAME: Zonen + SINGULARNAME: Zone + db_Description: Beschreibung + db_Name: Name + has_many_Regions: Regionen + ZoneAdmin: + MENUTITLE: Zonen + ZoneRegion: + PLURALNAME: "Zonen Regionen" + SINGULARNAME: "Zonen Region" + has_one_Zone: Zone + shop: + Checkout: Zahlungsart + STATUS: Status + CartEditField: + REMOVE: "Entfernen" + VARIATION: "Variationen" + CartForm: + UPDATE_CART: "Warenkorb aktualisieren" + REMOVED_ITEMS: "{count} Artikel entfernt" + UPDATED_ITEMS: "{count} Artikel aktualisiert." + OnsitePaymentCheckoutComponent: + CREDIT_CARD_INVALID: "Kreditkarte ist ungültig" + PaymentCheckoutComponent: + NO_PAYMENT_METHOD: "Bezahlmethode wurde nicht angegeben" + UNSUPPORTED_GATEWAY: "Bezahl-Gateway wird nicht unterstützt" + CheckoutStep: + # These should probably go somewhere else, as they are pretty generic. + CONTINUE: "Weiter" + SHIPPING: "Lieferung" + SUMMARY: "Zusammenfassung" + GRAND_TOTAL: "Gesamtbetrag" + CheckoutStep_Address: + SUPPLY_CONTACT_INFO: "Bitte geben Sie Ihre Kontaktdaten an" + SEPARATE_BILLING: "Abweichende Rechnungsadresse angeben" + ENTER_SHIPPING_ADDRESS: "Bitte geben Sie Ihre gewünschte Lieferadresse ein." + BILL_TO: "Rechnung an:" + SHIP_TO: "Lieferung an:" + CheckoutStep_Membership: + CONTINUE_AS_GUEST: "Weiter als Gast" + # This is an option presented to the user + CREATE_ACCOUNT: "Neues Konto erstellen" + # This is an action (Button label) + CREATE_NEW_ACCOUNT: 'Konto erstellen' + VariationForm: + CHOOSE_ATTRIBUTE: "Wähle {attribute} …" + VARIATION_NOT_AVAILABLE: "Diese Variante ist leider nicht verfügbar." + PRODUCT_NOT_AVAILABLE: "Das Produkt ist mit den ausgewählten Optionen nicht verfügbar." + PriceTag: + # Save as in savings (you saved this much from the original price) + SAVE: "Gespart" diff --git a/lang/en_GB.yml b/lang/legacy/en_GB.yml similarity index 100% rename from lang/en_GB.yml rename to lang/legacy/en_GB.yml diff --git a/lang/es.yml b/lang/legacy/es.yml similarity index 100% rename from lang/es.yml rename to lang/legacy/es.yml diff --git a/lang/es_MX.yml b/lang/legacy/es_MX.yml similarity index 100% rename from lang/es_MX.yml rename to lang/legacy/es_MX.yml diff --git a/lang/et_EE.yml b/lang/legacy/et_EE.yml similarity index 100% rename from lang/et_EE.yml rename to lang/legacy/et_EE.yml diff --git a/lang/fi.yml b/lang/legacy/fi.yml similarity index 100% rename from lang/fi.yml rename to lang/legacy/fi.yml diff --git a/lang/legacy/fr.yml b/lang/legacy/fr.yml new file mode 100644 index 000000000..ed29fe217 --- /dev/null +++ b/lang/legacy/fr.yml @@ -0,0 +1,189 @@ +--- +fr: + AccountPage: + Message: "Vous devez avoir un login avant d'avoir accès à votre page de profile. Si vous n'êtes pas enregistré, vous ne pourrez avoir accès qu'à partir du moment ou vous ferez votre première commande, sinon veuillez écrire vos renseignements ci-dessous." + NOPAGE: "Aucun compte sur ce site - créez-en un s'il vous plaît!" + AccountPage.ss: + COMPLETED: "Commandes terminées" + HISTORY: "Historique de vos commandes" + INCOMPLETE: "Commandes incomplètes" + NOCOMPLETED: "Aucune commande n'a été trouvée." + NOINCOMPLETE: "Aucune commande incomplète n'a été trouvée" + ORDER: "Commande #" + READMORE: "En savoir plus sur la commande #%s" + AccountPage_order.ss: + ADDRESS: Adresse + AMOUNT: Somme + BACKTOCHECKOUT: "Cliquer ici pour vous rendre à la page de la caisse" + CITY: Ville + COUNTRY: Pays + DATE: Date + DETAILS: Détails + EMAILDETAILS: "Une copie confirmant les détails a été envoyé vers votre adresse email." + NAME: Nom + PAYMENTMETHOD: Methode + PAYMENTSTATUS: "Statut du payement" + Cart.ss: + ADDONE: "Ajouter un ou plusieurs \"%s\" à votre panier" + CheckoutClick: "Cliquez ici pour passer au paiement" + CheckoutGoTo: "Passer au paiement" + HEADLINE: "Mon Panier" + NOITEMS: "Il ya aucun article dans votre panier" + PRICE: Prix + READMORE: "Cliquez ici pour en savoir plus sur \"%s\"" + REMOVE: "Enlève \"%s\" de votre commande" + REMOVEALL: "Supprimer tous les \"%s\" de votre panier" + REMOVEONE: "Supprimer un des \"%s\" de votre panier" + SUBTOTAL: Sous-Total + TOTAL: Total + CheckoutPage: + NOPAGE: "Pas de paiement sur la page de ce site, veuillez en créer un!" + CheckoutPage.ss: + CHECKOUT: Commander + ORDERSTATUS: "Etat de la commande" + PROCESS: Processus + ChequePayment: + MESSAGE: "Paiement par Chèque. Veuillez prendre note que les produits ne seront expédiés qu'après réception de votre paiement." + MemberForm: + DETAILSSAVED: "Vos coordonnées ont été enregistrées" + LOGGEDIN: "Vous êtes actuellement connecté en tant que" + Order: + INCOMPLETE: "Commande incomplète" + SUCCESSFULL: "Commande réussie" + OrderInformation.ss: + ADDRESS: Adresse + AMOUNT: Quantité + BUYERSADDRESS: "Adresse de l'acheteur" + CITY: Ville + COUNTRY: Pays + CUSTOMERDETAILS: "Détails du Client" + DATE: Date + DETAILS: Détails + EMAIL: "Courrier électronique" + MOBILE: GSM + NAME: Nom + ORDERSUMMARY: "Récapitulatif des commandes" + PAYMENTID: "ID du Paiement" + PAYMENTINFORMATION: "Information de Paiement" + PAYMENTMETHOD: Méthode + PAYMENTSTATUS: "Statut du Paiement" + PHONE: Téléphone + PRICE: Prix + PRODUCT: Produit + QUANTITY: Quantité + READMORE: "Cliquez ici pour lire plus de \"%s\"" + SHIPPINGDETAILS: "Détails des achats" + SUBTOTAL: Sous-Total + TOTAL: Total + TOTALOUTSTANDING: "Total encours" + TOTALPRICE: "Prix Total" + OrderInformation_Editable.ss: + ADDONE: "Ajoutez un \"%s\" à votre panier" + NOITEMS: "Il n'y a pas d'article dans votre panier." + ORDERINFORMATION: "Information Pour commander" + PRICE: Prix + PRODUCT: Produit + QUANTITY: Quantité + READMORE: "Cliquez ici pour lire la suite sur \"%s\"" + REMOVE: "Enlève \"%s\" de votre commande" + REMOVEALL: "Supprimer tous les \"%s\" du panier" + REMOVEONE: "Supprimez un \"%s\" de votre panier" + SUBTOTAL: Sous-Total + TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." + TOTAL: Total + TOTALPRICE: "Prix Total" + OrderInformation_NoPricing.ss: + ADDRESS: Adresse + BUYERSADDRESS: "Adresse de l'acheteur" + CITY: Ville + COUNTRY: Pays + CUSTOMERDETAILS: "Détails du Client" + EMAIL: "Courrier électronique" + MOBILE: GSM + NAME: Nom + ORDERINFO: "Information pour Commander #" + PHONE: Téléphone + TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." + OrderInformation_PackingSlip.ss: + DESCRIPTION: Description + ITEM: Article + ORDERDATE: "Date de la commande" + ORDERNUMBER: "Numéro de Commande" + PAGETITLE: "Imprimer la commande" + QUANTITY: Quantité + TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." + OrderInformation_Print.ss: + PAGETITLE: "Imprimez votre commande" + OrderReport: + CHANGESTATUS: "Changement d'état des commandes" + NOTEEMAIL: "Note/Courrier électronique" + SENDNOTETO: "Envoyer cette note à %s (%s)" + Order_Content.ss: + NOITEMS: "Il n'y a aucun article dans votre ordre." + PRICE: Prix + PRODUCT: Produit + QUANTITY: Quantité + READMORE: "Clique ici pour lire plus sur \"%s\"" + SUBTOTAL: Sous-total + TOTAL: Total + TOTALPRICE: "Prix Total" + Order_Member.ss: + ADDRESS: Adresse + CITY: Ville + COUNTRY: Pays + EMAIL: Email + MOBILE: Mobile + NAME: Nom + PHONE: Téléphone + Order_ReceiptEmail.ss: + HEADLINE: "Reçu pour votre commande sur ce magasin" + TITLE: "Réception de la boutique" + Order_StatusEmail.ss: + HEADLINE: "Changement du statut du magasin" + STATUSCHANGE: "Status modifié à \"%s\" pour la commande #" + TITLE: "Changement du statut du magasin" + PaymentInformation.ss: + AMOUNT: Montant + DATE: Date + DETAILS: Détails + PAYMENTID: "ID du Paiement" + PAYMENTINFORMATION: "Information sur le paiement" + PAYMENTMETHOD: Méthode + PAYMENTSTATUS: "Etat du paiement" + TABLESUMMARY: "Le contenu de votre panier est affiché dans ce formulaire, ainsi qu'un résumé de tous les frais associés à cet achat et un aperçu des options de paiements." + Product.ss: + ADD: "Ajouter \"%s\" dans votre panier" + ADDLINK: "Ajouter un article à votre panier" + ADDONE: "Ajouter un ou plusieurs \"%s\" de votre panier" + AUTHOR: Auteur + FEATURED: "Il s'agit d'une description du produit." + GOTOCHECKOUT: "Aller au paiement maintenant" + GOTOCHECKOUTLINK: "» Aller au paiement" + IMAGE: "%s image" + ItemID: "Article #" + NOIMAGE: "Désolé, pas d'image disponible du produit pour \"%s\"" + QUANTITYCART: "Quantité dans le panier" + REMOVE: "Supprimer \"%s\" de votre panier" + REMOVEALL: "Supprimez un \"%s\" de votre panier" + REMOVELINK: "» Retiré de votre panier" + SIZE: Taille + ProductCategory.ss: + FEATURED: "Produits " + OTHER: "Autres Produits" + ProductCategoryItem.ss: + ADD: "Ajouter un \"%s\" dans votre panier" + ADDLINK: "Ajouter un article au panier" + ADDONE: "Ajouter un ou plusieurs \"%s\" dans votre panier" + AUTHOR: Auteur + GOTOCHECKOUT: "Aller à la caisse maintenant" + GOTOCHECKOUTLINK: "» Aller à la caisse" + IMAGE: "%s image" + NOIMAGE: "Désolé, il n'y a pas d'image pour le produit \"%s\"" + QUANTITYCART: "Quantité dans le panier" + READMORE: "Cliquez ici pour en savoir plus sur \"%s\"" + READMORECONTENT: "CLiquez pour en savoir plus »" + REMOVE: "Supprimer un \"%s\" de votre panier" + REMOVEALL: "Supprimer un des \"%s\" du panier" + REMOVELINK: "» Retiré du panier" + ProductMenu.ss: + GOTOPAGE: "Aller à la page %s" diff --git a/lang/hr.yml b/lang/legacy/hr.yml similarity index 100% rename from lang/hr.yml rename to lang/legacy/hr.yml diff --git a/lang/id.yml b/lang/legacy/id.yml similarity index 100% rename from lang/id.yml rename to lang/legacy/id.yml diff --git a/lang/it.yml b/lang/legacy/it.yml similarity index 100% rename from lang/it.yml rename to lang/legacy/it.yml diff --git a/lang/ms.yml b/lang/legacy/ms.yml similarity index 100% rename from lang/ms.yml rename to lang/legacy/ms.yml diff --git a/lang/legacy/nb.yml b/lang/legacy/nb.yml new file mode 100644 index 000000000..5ee36b992 --- /dev/null +++ b/lang/legacy/nb.yml @@ -0,0 +1,273 @@ +--- +nb: + AccountNavigation: + AddressBook: Adressebok + EditProfile: "Endre profil" + LogOut: "Logg ut" + MemberEmail: Epost + MemberLastVisit: "Siste besøk" + MemberName: Navn + MemberSince: "Medlem siden" + NumberOfOrders: "Antall bestillinger" + PastOrders: "Ordre arkiv" + Title: "Min konto" + AccountPage: + Message: "Du må logge inn for å få tilgang til denne kontosiden. Hvis du er registrert, vil du ikke kunne få tilgang til denne før du har opprettet din første ordre. Ellers tast inn detaljene nedenunder." + NOPAGE: "Ingen kontoside på denne nettsiden, vennligst opprett en!" + NoPastOrders: "Det finnes ingen bestillingar." + Title: "Tidligere bestillingar" + AccountPage.ss: + COMPLETED: "Fullførte bestillinger" + HISTORY: "Din ordrehistorikk" + INCOMPLETE: "Ukomplette Ordre" + NOCOMPLETED: "Ingen komplette ordre ble funnet" + NOINCOMPLETE: "Ingen ukomplette ordre ble funnet" + ORDER: Bestilling + READMORE: "Les mer på Ordre #%s" + AccountPage_AddressBook: + CreateNewTitle: "Opprett ny adresse" + NoAddress: "Det finnes ingen adresse." + Title: Adresser + AccountPage_EditProfile: + Title: "Endre profil" + AccountPage_order.ss: + ADDRESS: Adresse + AMOUNT: Beløp + BACKTOCHECKOUT: "klikk her for å gå til kasse" + CITY: By + COUNTRY: Land + DATE: Dato + DETAILS: Detaljer + EMAILDETAILS: "En kopi av denne er sendt til din epost adresse for å bekrefte bestillingens innhold." + NAME: Navn + PAYMENTMETHOD: Metode + PAYMENTSTATUS: Betalingsstatus + Address: + ADDRESS: Adresse + ADDRESS2HINT: "bygning, leiligher, etasje, c.o." + ADDRESSHINT: "gatenavn, postboks" + ADDRESSLINE2: "Adresse 2 (valgfri)" + BillingAddress: Fakturaadresse + CITY: Sted + CITYHINT: "eller tettsted, bygg, by" + COUNTRY: Land + PHONE: Telefon + POSTALCODE: Postnummer + STATE: Fylke + STATEHINT: "eller øy, provins" + SaveDefaults: "Lagre addresser" + SaveNew: "Lagre ny adresse" + ShippingAddress: Postadresse + Cart.ss: + ADDONE: "Legg til en mer av \"%s\" til handlekurven." + CheckoutClick: "Klikk her for å gå til kassen" + CheckoutGoTo: "Gå til kassen" + HEADLINE: "Min handlevogn" + NOITEMS: "Handlevognen er tom" + PRICE: Pris + READMORE: "Klikk her for å lese mer om \"%s\"" + REMOVE: "Fjern \"%s\" fra din bestilling" + REMOVEALL: "Fjern alle av \"%s\" fra handlekurven" + REMOVEONE: "Fjern en av \"%s\" fra handlekurven" + SUBTOTAL: Delsum + TOTAL: Sum + CheckoutPage: + NOPAGE: "Finnes ingen ChecoutPage for dette nettstedet - vennligst opprett en!" + CheckoutPage.ss: + CHECKOUT: "Sjekk ut" + ORDERSTATUS: Bestillingsstatus + PROCESS: Prosess + ChequePayment: + MESSAGE: "Betalingen er akspetert med sjekk. Merk: varene vil ikke bli sendt før betalingen er motatt" + FreeShippingModifier: + FREE: Gratis + Member: + BUTTONCHANGEPASSWORD: "Endre passord" + CONFIRMNEWPASSWORD: "Bekreft nytt passord" + DATEFORMAT: Datoformat + EMAIL: Epost + FIRSTNAME: Navn + NEWPASSWORD: "Nytt passord" + SURNAME: Etternavn + TIMEFORMAT: Tidsformat + YOUROLDPASSWORD: "Ditt gamle passord" + db_Locale: Språk + MemberForm: + DETAILSSAVED: "Dine endringer er lagret" + LOGGEDIN: "Du er logget inn som" + SAVE: "Lagre endringer" + SAVEANDPROCEED: "Lagre og gå til betaling" + Order: + INCOMPLETE: "Ordre Ufullkommen" + ORDERNOTES: Note + SUCCESSFULL: "Ordre Vellykket" + TOTALOUTSTANDING: "Utestående beløp" + OrderHistory: + OrderDate: Dato + OrderItems: Antall + OrderReference: Referanse + OrderStatus: Status + OrderTotal: Total + ViewOrder: "vis bestilling" + OrderInformation.ss: + ADDRESS: Adresse + AMOUNT: Beløp + BUYERSADDRESS: Kundeadresse + CITY: By + COUNTRY: Land + CUSTOMERDETAILS: Kundedetaljer + DATE: Dato + DETAILS: Detaljer + EMAIL: E-postadresse + MOBILE: Mobilnr. + NAME: NAvn + ORDERSUMMARY: "Sammendrag av bestillingen" + PAYMENTID: Betalings-ID + PAYMENTINFORMATION: Betalingsinformasjon + PAYMENTMETHOD: Metode + PAYMENTSTATUS: Betalingsstatus + PHONE: Telefonnr. + PRICE: Pris + PRODUCT: Produkt + QUANTITY: Antall + READMORE: "Klikk her for å lese mer om \"%s\"" + SHIPPINGDETAILS: Fraktdetaljer + SUBTOTAL: Delsum + TOTAL: Total + TOTALOUTSTANDING: "Totalt utestående" + TOTALPRICE: Totalpris + OrderInformation_Editable.ss: + ADDONE: "Legg til en til av \"%s\" til handelvognen" + NOITEMS: "Det er ingenting i handlekurven." + ORDERINFORMATION: Bestillingsinformasjon + PRICE: Pris + PRODUCT: Produkt + QUANTITY: Antall + READMORE: "Klikk her for å lese mer om \"%s\"" + REMOVE: "Fjern \"%s\" fra din bestilling" + REMOVEALL: "Fjern alle \"%s\" fra din handlevogn" + REMOVEONE: "Fjern en av \"%s\" fra handlevognen" + SUBTOTAL: Delsum + TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." + TOTAL: Sum + TOTALPRICE: Totalpris + OrderInformation_NoPricing.ss: + ADDRESS: Adresse + BUYERSADDRESS: "Kjøpers adresse" + CITY: By + COUNTRY: Land + CUSTOMERDETAILS: Kundedetaljer + EMAIL: E-postadresse + MOBILE: Mobilnummer + NAME: Navn + ORDERINFO: "Informasjon om bestilling #" + PHONE: Telefonnummer + TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." + OrderInformation_PackingSlip.ss: + DESCRIPTION: Beskrivelse + ITEM: Enhet + ORDERDATE: Bestillingsdato + ORDERNUMBER: Bestillingsnummer + PAGETITLE: "Butikk Print Ordre" + QUANTITY: Antall + TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." + OrderInformation_Print.ss: + PAGETITLE: "Skriv ut bestillinger" + OrderReport: + CHANGESTATUS: "Endre bestillingsstatus" + NOTEEMAIL: Merknad/e-post + SENDNOTETO: "Send merknaden til %s (%s)" + Order_Address.ss: + BILLTO: Fakturaadresse + SHIPTO: Fraktadresse + Order_Content.ss: + NOITEMS: "Det er ingenvarer i din bestilling" + PRICE: Pris + PRODUCT: Produkt + QUANTITY: Antall + READMORE: "Klikk her for å lese mere på \"%s\"" + SUBTOTAL: Sub-total + TOTAL: Total + TOTALOUTSTANDING: "Totalt utestående" + TOTALPRICE: Total + UNITPRICE: Pris + Order_Content_ItemLine.ss: + READMORE: "Se mer om \"%s\"" + Order_Content_SubTotals.ss: + SUBTOTAL: "Total ink mva" + TOTAL: Total + Order_Member.ss: + ADDRESS: Adresse + CITY: By + COUNTRY: Land + EMAIL: Epost + MOBILE: Mobil + NAME: Navn + PHONE: Telefon + Order_Payments.ss: + AMOUNT: Sum + DATE: Dato + PAYMENTMETHOD: Betalingsmåte + PAYMENTNOTE: Melding + PAYMENTS: Betalingar + PAYMENTSTATUS: Status + Order_ReceiptEmail.ss: + HEADLINE: Bestillingskvittering + TITLE: "Handle kvittering" + Order_StatusEmail.ss: + HEADLINE: "Handlestatus endres" + STATUSCHANGE: "Status endret til \"%s\" for bestilling #" + TITLE: "Handlestatus endres" + PaymentInformation.ss: + AMOUNT: Beløp + DATE: Dato + DETAILS: Detaljer + PAYMENTID: Betalings-ID + PAYMENTINFORMATION: Betalingsinformasjon + PAYMENTMETHOD: Metode + PAYMENTSTATUS: Betalingsstatus + TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." + PickupShippingModifier: + SINGULAR: "Hent i butikk" + Product.ss: + ADD: "Legg til \"%s\" til din handlekurv" + ADDLINK: "Legg i handlekurv" + ADDONE: "Legg til en mer av \"%s\" til din handlekurv" + AUTHOR: Forfatter + FEATURED: "Dette er et anbefalt produkt" + GOTOCHECKOUT: "Gå til kassen" + GOTOCHECKOUTLINK: "» Gå til kassen" + IMAGE: "%s bilde" + ItemID: "Vare #" + NOIMAGE: "Beklager, ingen produktbilde for \"%s\"" + QUANTITYCART: "Antall i handlekurv" + REMOVE: "Fjern \"%s\" fra din handlekurv" + REMOVEALL: "Fjern en av \"%s\" fra din handlekurv" + REMOVELINK: "» Fjern fra handlekurv" + SIZE: Størrelse + ProductCategory.ss: + FEATURED: "Anbefalte produkter" + OTHER: "Andre produkter" + ProductCategoryItem.ss: + ADD: "Legg \"%s\" i handlekurven" + ADDLINK: "Legg i handlevognen" + ADDONE: "Legg til en til av \"%s\" i handlevognen" + AUTHOR: Forfatter + GOTOCHECKOUT: "Gå til kassa nå" + GOTOCHECKOUTLINK: "» Gå til kassa" + IMAGE: "%s bilde" + NOIMAGE: "Beklager, det finnes ikke noe bilde av \"%s\"" + QUANTITYCART: "Antall i handlevogn" + READMORE: "Trykk her for å lese mer om \"%s\"" + READMORECONTENT: "Trykk her for å lese mer »" + REMOVE: "Fjern \"%s\" fra handlekurven" + REMOVEALL: "Fjern en \"%s\" fra din handlevogn" + REMOVELINK: "» Fjern fra handlevognen" + ProductMenu.ss: + GOTOPAGE: "Gå til %s" + ShippingModifier: + SINGULAR: Frakt + SimpleShippingModifier: + SHIPTO: "Send til %s" + WeightShippingModifier: + TABLETITLE: "Frakt (%f kg)" diff --git a/lang/nl.yml b/lang/legacy/nl.yml similarity index 100% rename from lang/nl.yml rename to lang/legacy/nl.yml diff --git a/lang/legacy/nn.yml b/lang/legacy/nn.yml new file mode 100644 index 000000000..aaad168b9 --- /dev/null +++ b/lang/legacy/nn.yml @@ -0,0 +1,95 @@ +--- +nn: + AccountNavigation: + AddressBook: Adressebok + EditProfile: "Endre profil" + LogOut: "Logg ut" + MemberEmail: Epost + MemberLastVisit: "Siste besøk" + MemberName: Namn + MemberSince: "Medlem sida" + NumberOfOrders: "Antal bestillingar" + PastOrders: "Ordre arkiv" + Title: "Min konto" + AccountPage: + NoPastOrders: "Det finnes ingen bestillingar." + Title: "Tidligere bestillingar" + AccountPage.ss: + ORDER: Bestilling + AccountPage_AddressBook: + CreateNewTitle: "Opprett ny adresse" + NoAddress: "Det finnast inga adresse." + Title: Adresser + AccountPage_EditProfile: + Title: "Endre profil" + Address: + ADDRESS: Adresse + ADDRESS2HINT: "bygning, leiligher, etasje, c.o." + ADDRESSHINT: "gatenavn, postboks" + ADDRESSLINE2: "Adresse 2 (valfri)" + BillingAddress: Fakturaadresse + CITY: Sted + CITYHINT: "eller tettsted, bygg, by" + COUNTRY: Land + PHONE: Telefon + POSTALCODE: Postnummer + STATE: Fylke + STATEHINT: "eller øy, provins" + SaveDefaults: "Lagre addressar" + SaveNew: "Lagre ny adresse" + ShippingAddress: Postadresse + FreeShippingModifier: + FREE: Gratis + Member: + BUTTONCHANGEPASSWORD: "Endre passord" + CONFIRMNEWPASSWORD: "Bekreft nytt passord" + DATEFORMAT: Datoformat + EMAIL: Epost + FIRSTNAME: Navn + NEWPASSWORD: "Nytt passord" + SURNAME: Etternavn + TIMEFORMAT: Tidsformat + YOUROLDPASSWORD: "Ditt gamle passord" + db_Locale: Språk + MemberForm: + DETAILSSAVED: "Dine endringer er lagret" + SAVE: "Lagre endringer" + SAVEANDPROCEED: "Lagre og gå til betaling" + Order: + ORDERNOTES: Note + TOTALOUTSTANDING: "Utestående beløp" + OrderHistory: + OrderDate: Dato + OrderItems: Antall + OrderReference: Referanse + OrderStatus: Status + OrderTotal: Total + ViewOrder: "vis bestilling" + Order_Address.ss: + BILLTO: Fakturaadresse + SHIPTO: Fraktadresse + Order_Content.ss: + PRODUCT: Produkt + QUANTITY: Antall + TOTALPRICE: Total + UNITPRICE: Pris + Order_Content_ItemLine.ss: + READMORE: "Sjå meir om \\\"%s\\\"" + Order_Content_SubTotals.ss: + SUBTOTAL: "Total ink mva" + TOTAL: Total + Order_Payments.ss: + AMOUNT: Sum + DATE: Dato + PAYMENTMETHOD: Betalingsmåte + PAYMENTNOTE: Melding + PAYMENTS: Betalingar + PAYMENTSTATUS: Status + PickupShippingModifier: + SINGULAR: "Hent i butikk" + ShippingModifier: + SINGULAR: Frakt + SimpleShippingModifier: + SHIPTO: "Send til %s" + WeightShippingModifier: + TABLETITLE: "Frakt (%f kg)" diff --git a/lang/pl.yml b/lang/legacy/pl.yml similarity index 100% rename from lang/pl.yml rename to lang/legacy/pl.yml diff --git a/lang/pt.yml b/lang/legacy/pt.yml similarity index 100% rename from lang/pt.yml rename to lang/legacy/pt.yml diff --git a/lang/ru.yml b/lang/legacy/ru.yml similarity index 100% rename from lang/ru.yml rename to lang/legacy/ru.yml diff --git a/lang/tr.yml b/lang/legacy/tr.yml similarity index 100% rename from lang/tr.yml rename to lang/legacy/tr.yml diff --git a/lang/zh_TW.yml b/lang/legacy/zh_TW.yml similarity index 100% rename from lang/zh_TW.yml rename to lang/legacy/zh_TW.yml diff --git a/lang/nb.yml b/lang/nb.yml index 5ee36b992..0a582b0a1 100644 --- a/lang/nb.yml +++ b/lang/nb.yml @@ -1,273 +1,492 @@ ---- nb: - AccountNavigation: - AddressBook: Adressebok - EditProfile: "Endre profil" - LogOut: "Logg ut" - MemberEmail: Epost - MemberLastVisit: "Siste besøk" - MemberName: Navn - MemberSince: "Medlem siden" - NumberOfOrders: "Antall bestillinger" - PastOrders: "Ordre arkiv" - Title: "Min konto" + OrdersAdmin: + MENUTITLE: Ordrer + ProductCatalogAdmin: + MENUTITLE: Katalog + ZoneAdmin: + MENUTITLE: Soner + AbandonedCartReport: + Description: 'Overvåk forlatte handlekurver for en bestemt periode. Grupper resultatene for et år, måned eller dag.' + Title: 'Forlatte handlekurver' AccountPage: - Message: "Du må logge inn for å få tilgang til denne kontosiden. Hvis du er registrert, vil du ikke kunne få tilgang til denne før du har opprettet din første ordre. Ellers tast inn detaljene nedenunder." - NOPAGE: "Ingen kontoside på denne nettsiden, vennligst opprett en!" - NoPastOrders: "Det finnes ingen bestillingar." - Title: "Tidligere bestillingar" - AccountPage.ss: - COMPLETED: "Fullførte bestillinger" - HISTORY: "Din ordrehistorikk" - INCOMPLETE: "Ukomplette Ordre" - NOCOMPLETED: "Ingen komplette ordre ble funnet" - NOINCOMPLETE: "Ingen ukomplette ordre ble funnet" - ORDER: Bestilling - READMORE: "Les mer på Ordre #%s" + AddressBook: 'Adressebok' + DESCRIPTION: 'Gi bruker tilgang til å se sine kontodetaljer og ordrer.' + DefaultTitle: Konto + EditProfile: 'Rediger profil' + LogOut: 'Logg ut' + Login: "Du må være logget inn for å få tilgang til kontoen. Om du ikke er registrert vil du få tilbud om det når bestiller din første ordre." + LoginAgain: 'Du er blitt logget ut. Logg inn igjen om du vil!' + MemberEmail: E-post + MemberLastVisit: 'Siste besøk' + MemberName: Navn + MemberSince: 'Medlem siden' + NoPage: 'Ingen Kontoside ble funnet. Vennligst lag ei i adminpanlet.' + NoPastOrders: 'Ingen ordrer ble funnet. ' + NumberOfOrders: 'Antall ordrer' + PLURALNAME: 'Kontosider' + PastOrders: 'Tidligere ordrer' + SINGULARNAME: 'Kontoside' + Title: 'Min konto' AccountPage_AddressBook: - CreateNewTitle: "Opprett ny adresse" - NoAddress: "Det finnes ingen adresse." - Title: Adresser + CreateNewTitle: 'Lag en ny adresse' + NoAddress: 'Det finnes ingen adresse' + Title: 'Standard adresse' + DefaultShippingAddress: 'Standard fraktadresse' + DefaultBillingAddress: 'Standard betalingsadresse' + MakeDefaultShipping: 'Gjør denne til standard frakt' + MakeDefaultShippingTitle: 'Gjør denne til standard fraktadresse' + MakeDefaultBilling: 'Gjør denne til standard betaling' + MakeDefaultBillingTitle: 'Gjør denne til standard betalingadresse' + DeleteAddress: 'Slett adresse' AccountPage_EditProfile: - Title: "Endre profil" - AccountPage_order.ss: - ADDRESS: Adresse - AMOUNT: Beløp - BACKTOCHECKOUT: "klikk her for å gå til kasse" - CITY: By - COUNTRY: Land - DATE: Dato - DETAILS: Detaljer - EMAILDETAILS: "En kopi av denne er sendt til din epost adresse for å bekrefte bestillingens innhold." - NAME: Navn - PAYMENTMETHOD: Metode - PAYMENTSTATUS: Betalingsstatus + Title: 'Endre profil' Address: - ADDRESS: Adresse - ADDRESS2HINT: "bygning, leiligher, etasje, c.o." - ADDRESSHINT: "gatenavn, postboks" - ADDRESSLINE2: "Adresse 2 (valgfri)" - BillingAddress: Fakturaadresse - CITY: Sted - CITYHINT: "eller tettsted, bygg, by" - COUNTRY: Land - PHONE: Telefon - POSTALCODE: Postnummer - STATE: Fylke - STATEHINT: "eller øy, provins" - SaveDefaults: "Lagre addresser" - SaveNew: "Lagre ny adresse" - ShippingAddress: Postadresse - Cart.ss: - ADDONE: "Legg til en mer av \"%s\" til handlekurven." - CheckoutClick: "Klikk her for å gå til kassen" - CheckoutGoTo: "Gå til kassen" - HEADLINE: "Min handlevogn" - NOITEMS: "Handlevognen er tom" - PRICE: Pris - READMORE: "Klikk her for å lese mer om \"%s\"" - REMOVE: "Fjern \"%s\" fra din bestilling" - REMOVEALL: "Fjern alle av \"%s\" fra handlekurven" - REMOVEONE: "Fjern en av \"%s\" fra handlekurven" - SUBTOTAL: Delsum - TOTAL: Sum + AddressHint: 'gate / nummer, navn og type eller postboks ' + AddressLine2Hint: 'byggning, leilighet, etasje' + BillingAddress: 'Betalingsadresse' + CityHint: 'sted, kommune' + CreateNewAddress: 'Lag ny adresse' + ExistingBillingAddress: 'Eksisterende betalingsadresse' + ExistingShippingAddress: 'Eksisterende fraktadresse' + PLURALNAME: Adresser + SINGULARNAME: Adresse + SaveDefaults: 'Lagre som standard' + SaveNew: 'Lagre ny adresse' + ShippingAddress: 'Fraktadresse' + StateHint: 'eller provins, område, øy' + db_Address: Adresse + db_AddressLine2: 'Adresse linje 2 (valgfri) ' + db_City: By + db_Company: Bedrift + db_Country: Land + db_FirstName: 'Fornavn' + db_Phone: 'Telefonnummer' + db_PostalCode: 'Postkode' + db_State: Fylke + db_Surname: Etternavn + has_one_Member: Medlem + CartForm: + REMOVED_ITEMS: 'Fjernet {count} produkt.' + UPDATED_ITEMS: 'Oppdater {count} produkt.' + UpdateCart: 'Oppdater handlekurv' + CartPage: + DESCRIPTION: 'En side hvor en kunde kan se og redigere innholdet i handlekurven sin.' + DefaultTitle: 'Handlekurv' + PLURALNAME: 'Handlekurvsider' + SINGULARNAME: 'Handlekurvside' + has_one_CheckoutPage: 'Kasseside' + has_one_ContinuePage: 'Fortsettside' + Checkout: + IdFieldNotFound: 'Obligatorisk felt er ikke funnet: {IdentifierField}' + MemberExists: 'Et medlem finnes allerede med {Field} {Identifier}' + MembershipIsNotAllowed: 'Det stengt for å lage ny konto' + NoPaymentMethod: 'Betalingsmåte eksisterer ikke' + PasswordRequired: 'Det er nødvendig med et passord' + TermsAndConditionsLink: 'Jeg godtar salgsvilkår {TermsPageTitle} page' + CheckoutComponentValidator: + InvalidDataMessage: 'Noe av dataen du har skrevet er feil, se under:' + CheckoutField: + AccountInfo: 'Vennligst vel et passord, du kan senere logge inn og se ordrehistorikken din' + MemberLoginInfo: 'Hvis du allerede har en konto, vennligst logg inn' + MembershipDetails: 'Kontodetaljer' + MustAgreeToTerms: 'Du må godta vilkår og betalingsbetingelser' + Password: Passord + PaymentType: 'Betalingsmåte' CheckoutPage: - NOPAGE: "Finnes ingen ChecoutPage for dette nettstedet - vennligst opprett en!" - CheckoutPage.ss: - CHECKOUT: "Sjekk ut" - ORDERSTATUS: Bestillingsstatus - PROCESS: Prosess - ChequePayment: - MESSAGE: "Betalingen er akspetert med sjekk. Merk: varene vil ikke bli sendt før betalingen er motatt" + DESCRIPTION: 'Siden som behandler kasse funksjonen ' + DefaultTitle: Kasse + PLURALNAME: 'Kasseside' + PaymentErrorMessage: 'En feil skjedde ved betaling, error fra betaling gateway:' + ProceedToPayment: 'Fortsett til betaling' + PurchaseCompleteDescription: 'Denne beskjeden blir inkludert i kvittering som blir sendt på epost etter at ordren er betalt.' + SINGULARNAME: 'Kasseside' + SubmitPayment: 'Send betaling' + db_PurchaseComplete: 'Ordre er betalt' + CheckoutStep: + Continue: Fortsett + Shipping: Frakt + Summary: Oppsummering + CheckoutStep_Address: + BillTo: 'Send regning til:' + EnterShippingAddress: 'Venligst skriv inn din fraktadresse' + SeperateBilling: 'Send regning til en annen adresse' + ShipTo: 'Send forsendelse til:' + SupplyContactInformation: 'Skriv inn din kontaktinformasjon' + CheckoutStep_Membership: + ContinueAsGuest: 'Fortsett som gjest' + CreateAccount: 'Lag en konto' + CreateNewAccount: 'Lag en ny konto' + CreateAddressForm: + AddressSaved: 'Din adresse er lagret' + CustomerReport: + Description: 'Kunden sin rapport' + Title: Kunden + FlatTaxModifier: + PLURALNAME: Momsen + SINGULARNAME: Moms FreeShippingModifier: - FREE: Gratis - Member: - BUTTONCHANGEPASSWORD: "Endre passord" - CONFIRMNEWPASSWORD: "Bekreft nytt passord" - DATEFORMAT: Datoformat - EMAIL: Epost - FIRSTNAME: Navn - NEWPASSWORD: "Nytt passord" - SURNAME: Etternavn - TIMEFORMAT: Tidsformat - YOUROLDPASSWORD: "Ditt gamle passord" - db_Locale: Språk + Free: Gratis + PLURALNAME: 'Gratis fraktmodifikator' + SINGULARNAME: Frakt + GlobalTaxModifier: + Included: '(inkludert i prisen over)' + PLURALNAME: Momsen + SINGULARNAME: Moms + db_Country: Land MemberForm: - DETAILSSAVED: "Dine endringer er lagret" - LOGGEDIN: "Du er logget inn som" - SAVE: "Lagre endringer" - SAVEANDPROCEED: "Lagre og gå til betaling" + DetailsSaved: 'Dine detaljer er lagret' + Save: 'Lagre endringer' + SaveAndProceed: 'Lagre og fortsett til kassen' + OnsitePaymentCheckoutComponent: + InvalidCreditCard: 'Kredittkort er ugyldig' Order: - INCOMPLETE: "Ordre Ufullkommen" - ORDERNOTES: Note - SUCCESSFULL: "Ordre Vellykket" - TOTALOUTSTANDING: "Utestående beløp" - OrderHistory: - OrderDate: Dato - OrderItems: Antall - OrderReference: Referanse - OrderStatus: Status - OrderTotal: Total - ViewOrder: "vis bestilling" - OrderInformation.ss: - ADDRESS: Adresse - AMOUNT: Beløp - BUYERSADDRESS: Kundeadresse - CITY: By - COUNTRY: Land - CUSTOMERDETAILS: Kundedetaljer - DATE: Dato - DETAILS: Detaljer - EMAIL: E-postadresse - MOBILE: Mobilnr. - NAME: NAvn - ORDERSUMMARY: "Sammendrag av bestillingen" - PAYMENTID: Betalings-ID - PAYMENTINFORMATION: Betalingsinformasjon - PAYMENTMETHOD: Metode - PAYMENTSTATUS: Betalingsstatus - PHONE: Telefonnr. - PRICE: Pris - PRODUCT: Produkt - QUANTITY: Antall - READMORE: "Klikk her for å lese mer om \"%s\"" - SHIPPINGDETAILS: Fraktdetaljer - SUBTOTAL: Delsum - TOTAL: Total - TOTALOUTSTANDING: "Totalt utestående" - TOTALPRICE: Totalpris - OrderInformation_Editable.ss: - ADDONE: "Legg til en til av \"%s\" til handelvognen" - NOITEMS: "Det er ingenting i handlekurven." - ORDERINFORMATION: Bestillingsinformasjon - PRICE: Pris - PRODUCT: Produkt - QUANTITY: Antall - READMORE: "Klikk her for å lese mer om \"%s\"" - REMOVE: "Fjern \"%s\" fra din bestilling" - REMOVEALL: "Fjern alle \"%s\" fra din handlevogn" - REMOVEONE: "Fjern en av \"%s\" fra handlevognen" - SUBTOTAL: Delsum - TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." - TOTAL: Sum - TOTALPRICE: Totalpris - OrderInformation_NoPricing.ss: - ADDRESS: Adresse - BUYERSADDRESS: "Kjøpers adresse" - CITY: By - COUNTRY: Land - CUSTOMERDETAILS: Kundedetaljer - EMAIL: E-postadresse - MOBILE: Mobilnummer - NAME: Navn - ORDERINFO: "Informasjon om bestilling #" - PHONE: Telefonnummer - TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." - OrderInformation_PackingSlip.ss: - DESCRIPTION: Beskrivelse - ITEM: Enhet - ORDERDATE: Bestillingsdato - ORDERNUMBER: Bestillingsnummer - PAGETITLE: "Butikk Print Ordre" - QUANTITY: Antall - TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." - OrderInformation_Print.ss: - PAGETITLE: "Skriv ut bestillinger" - OrderReport: - CHANGESTATUS: "Endre bestillingsstatus" - NOTEEMAIL: Merknad/e-post - SENDNOTETO: "Send merknaden til %s (%s)" - Order_Address.ss: - BILLTO: Fakturaadresse - SHIPTO: Fraktadresse - Order_Content.ss: - NOITEMS: "Det er ingenvarer i din bestilling" - PRICE: Pris - PRODUCT: Produkt - QUANTITY: Antall - READMORE: "Klikk her for å lese mere på \"%s\"" - SUBTOTAL: Sub-total - TOTAL: Total - TOTALOUTSTANDING: "Totalt utestående" - TOTALPRICE: Total - UNITPRICE: Pris - Order_Content_ItemLine.ss: - READMORE: "Se mer om \"%s\"" - Order_Content_SubTotals.ss: - SUBTOTAL: "Total ink mva" - TOTAL: Total - Order_Member.ss: - ADDRESS: Adresse - CITY: By - COUNTRY: Land - EMAIL: Epost - MOBILE: Mobil - NAME: Navn - PHONE: Telefon - Order_Payments.ss: - AMOUNT: Sum - DATE: Dato - PAYMENTMETHOD: Betalingsmåte - PAYMENTNOTE: Melding - PAYMENTS: Betalingar - PAYMENTSTATUS: Status - Order_ReceiptEmail.ss: - HEADLINE: Bestillingskvittering - TITLE: "Handle kvittering" - Order_StatusEmail.ss: - HEADLINE: "Handlestatus endres" - STATUSCHANGE: "Status endret til \"%s\" for bestilling #" - TITLE: "Handlestatus endres" - PaymentInformation.ss: - AMOUNT: Beløp - DATE: Dato - DETAILS: Detaljer - PAYMENTID: Betalings-ID - PAYMENTINFORMATION: Betalingsinformasjon - PAYMENTMETHOD: Metode - PAYMENTSTATUS: Betalingsstatus - TABLESUMMARY: "Innholdet i handlekurven vises i denne seksjonen pluss oppsummering av alle avgifter og valg assossiert med en ordre." + BillTo: 'Regning til:' + Date: Dato + DateFrom: 'Dato fra' + DateTo: 'Dato til' + GrandTotal: 'Pris total' + Invoice: Faktura + OrderHeadline: 'Ordre #{OrderNo} {OrderDate}' + Outstanding: Utestående + OutstandingWithAmount: 'Utestående beløp: {Amount}' + PLURALNAME: Ordrer + Print: Print + Quantity: Antall + SINGULARNAME: Ordre + STATUS_ADMINCANCELLED: 'Kansellert av admin' + STATUS_CART: Handlekurv + STATUS_COMPLETE: Ferdig + STATUS_MEMBERCANCELLED: 'Kansellert av bruker' + STATUS_PAID: Betalt + STATUS_PROCESSING: Under behandling + STATUS_SENT: Sendt + STATUS_UNPAID: Ubetalt + ShipTo: 'Send til' + SubTotal: Sum total + Total: Pris + TotalOutstanding: 'Total utestående' + TotalPrice: 'Pris' + TotalPriceWithCurrency: 'Pris ({Currency})' + UnitPrice: 'Enhet pris' + db_Dispatched: Pakke sendt + db_Email: E-post + db_FirstName: 'Fornavn' + db_IPAddress: 'IP-adresse' + db_Notes: Beskjed + db_Paid: Betalt + db_Placed: Plassert + db_Printed: Printet + db_ReceiptSent: 'Kvittering sendt' + db_Reference: Referanse + db_SeparateBillingAddress: 'Egen betalingsadresse' + db_Status: Status + db_Surname: Etternavn + db_Total: Pris + has_many_Items: Produkter + has_many_Modifiers: Modifikatorer + has_many_OrderStatusLogs: 'Ordrestatus log' + has_many_Payments: Betalinger + has_one_BillingAddress: 'Betalingsadresse' + has_one_Member: Medlem + has_one_ShippingAddress: 'Fraktadresse' + OrderActionsForm: + CancelOrder: 'Avbestill ordre' + MakePayment: 'Betal' + ManualNotAllowed: 'Manuell betaling er ikke tillatt' + PayOrder: 'Betal utestående balanse' + PaymentMethod: 'Betalingmåte' + OrderAdmin: + ReceiptTitle: '{SiteTitle} Ordre {OrderNo}' + OrderAttribute: + PLURALNAME: Atributter + SINGULARNAME: Attributt + db_CalculatedTotal: 'Kalkulert total' + has_one_Order: Ordrer + OrderForm: + CouldNotProcessPayment: 'Betaling kunne ikke bli gjennomført' + OrderCancelled: 'Ordre er avbestilt' + OrderItem: + PLURALNAME: Produkter + SINGULARNAME: Produkt + db_Quantity: Antall + db_UnitPrice: 'Enhet pris' + OrderModifier: + PLURALNAME: Modifikatorer + SINGULARNAME: Modifikatorer + db_Amount: Beløp + db_Sort: Sortere + db_Type: Type + OrderProcessor: + NoItems: 'Ordre har ingen produkter' + NoOrder: 'Ordre finnes ikke.' + NoOrderStarted: 'Den nye ordren har ikke begynt ennå' + NotCart: 'Ordre er ikke ei handlekurv.' + OrderStatusLog: + PLURALNAME: 'Ordrestatuslogglinje' + SINGULARNAME: 'Ordrelogglinje' + db_DispatchTicket: 'Send billett' + db_DispatchedBy: 'Sendt av' + db_DispatchedOn: 'Sendt den' + db_Note: Melding + db_PaymentCode: 'Betalingskode' + db_PaymentOK: 'Betaling OK' + db_SentToCustomer: 'Send til kunde' + db_Title: Tittel + has_one_Author: Forfatter + has_one_Order: Ordre + Payment: + Note: Melding + PaymentsHeadline: Betaling(er) + PaymentCheckoutComponent: + NoPaymentMethod: 'Betlingsmåte er ikke gitt' + UnsupportedGateway: 'Gateway er ikke støttet' + PaymentProcessor: + CantPay: "Ordre kan ikke bli betalt for" + InvalidGateway: "`{gateway}` er ikke en godkjent gateway." PickupShippingModifier: - SINGULAR: "Hent i butikk" - Product.ss: - ADD: "Legg til \"%s\" til din handlekurv" - ADDLINK: "Legg i handlekurv" - ADDONE: "Legg til en mer av \"%s\" til din handlekurv" - AUTHOR: Forfatter - FEATURED: "Dette er et anbefalt produkt" - GOTOCHECKOUT: "Gå til kassen" - GOTOCHECKOUTLINK: "» Gå til kassen" - IMAGE: "%s bilde" - ItemID: "Vare #" - NOIMAGE: "Beklager, ingen produktbilde for \"%s\"" - QUANTITYCART: "Antall i handlekurv" - REMOVE: "Fjern \"%s\" fra din handlekurv" - REMOVEALL: "Fjern en av \"%s\" fra din handlekurv" - REMOVELINK: "» Fjern fra handlekurv" - SIZE: Størrelse - ProductCategory.ss: - FEATURED: "Anbefalte produkter" - OTHER: "Andre produkter" - ProductCategoryItem.ss: - ADD: "Legg \"%s\" i handlekurven" - ADDLINK: "Legg i handlevognen" - ADDONE: "Legg til en til av \"%s\" i handlevognen" - AUTHOR: Forfatter - GOTOCHECKOUT: "Gå til kassa nå" - GOTOCHECKOUTLINK: "» Gå til kassa" - IMAGE: "%s bilde" - NOIMAGE: "Beklager, det finnes ikke noe bilde av \"%s\"" - QUANTITYCART: "Antall i handlevogn" - READMORE: "Trykk her for å lese mer om \"%s\"" - READMORECONTENT: "Trykk her for å lese mer »" - REMOVE: "Fjern \"%s\" fra handlekurven" - REMOVEALL: "Fjern en \"%s\" fra din handlevogn" - REMOVELINK: "» Fjern fra handlevognen" - ProductMenu.ss: - GOTOPAGE: "Gå til %s" + PLURALNAME: Modifikatorer + SINGULARNAME: 'Hent i butikk' + PriceTag: + SAVE: Lagre + Product: + AddToCart: 'Legg til handlekurv' + AddToCartTitle: 'Legg "{Title}" til handlekurven' + AdditionalCategories: 'Lignende kategorier' + AllowPurchase: 'Tillat at produkt kan kjøpes' + Category: Kategori + CategoryDescription: 'Dette er ei foreldreside eller standardkategori.' + Code: 'Produktkode' + CostPriceDescription: 'Veiledende pris, leverandørpris eller pris før varen har blitt rabattert.' + DESCRIPTION: 'Produktside i butikken' + Featured: 'Fremhevet produkt' + Image: 'Produktbilde' + ImageAltText: '{Title} bilde' + InternalItemID: 'InternID' + Model: Modell + NoImage: 'ingen bilde' + PLURALNAME: Produkt + PageTitle: 'Produkttittel' + Price: Pris + PriceDesc: 'Prisen varen blir solgt for (salgspris).' + ProductCodeShort: SKU + SINGULARNAME: Produkt + Size: Størrelse + View: 'Vis produkt' + db_AllowPurchase: 'Tillat kjøp' + db_BasePrice: Pris + db_CostPrice: 'Veiledende pris' + db_Depth: Dybde + db_Featured: Fremhevet + db_Height: Høyde + db_InternalItemID: 'Intern-ID' + db_Model: Modell + db_Popularity: Popularitet + db_Weight: Vekt + db_Width: Bredde + has_many_Variations: Varianter + has_one_Image: Bilde + many_many_ProductCategories: 'Produktkategorier' + many_many_VariationAttributeTypes: 'Variasjonattributttype' + ProductAttributeType: + PLURALNAME: Atributter + SINGULARNAME: Attributt + SaveFirstInfo: 'Du må lagre først, så kan du legge til verdier' + belongs_many_many_Product: Produkt + db_Label: Merkelapp + db_Name: Navn + has_many_Values: Verdier + ProductAttributeValue: + PLURALNAME: Verdier + SINGULARNAME: Verdi + belongs_many_many_ProductVariation: 'Produktvariant' + db_Sort: Om rokere + db_Value: Verdier + has_one_Type: Type + ProductCategory: + DESCRIPTION: 'Produktkategori i butikken' + PLURALNAME: Kategorier + SINGULARNAME: Kategori + belongs_many_many_Products: Produkter + ProductGroup: + Next: neste + Page: Side + Previous: tilbake + ViewNext: 'Vis neste side' + ViewPage: 'Vis side {pageNum}' + ViewPrevious: 'Vis forrige side' + ProductMenu: + GotoPageTitle: 'Gå til {Title} ' + ProductReport: + Description: "Forstå hvilke produkt som gjøre det bra og ikke" + Title: Produkter + ProductVariation: + MustSaveFirstMessage: 'Du kan velge en variant-attributt etter du har lagret første gang, om det finnes en. ' + NoAttributeValuesMessage: '{attribute} har ingen verdier. Du kan lage dem hos "Products" > "Product Attribute Type" seksjonen i adminpanelet.' + PLURALNAME: Varianter + SINGULARNAME: Variant + db_InternalItemID: 'Intern ID' + db_Price: Pris + db_Version: Versjon + has_one_Image: Bilde + has_one_Product: Produkt + many_many_AttributeValues: 'Attributtverdi' + ProductVariation_OrderItem: + PLURALNAME: Produkter + SINGULARNAME: Produkt + db_ProductVariationVersion: 'Produktvariantversjon' + has_one_ProductVariation: 'Produktvariant' + ProductVariationsExtension: + Attributes: Attributter + AttributesDescription: 'Disse feltene viser måten hver variasjon varierer. Etter at en av de er valgt, kan man redigere hver variasjon.' + Variations: Varianter + VariationsInfo: 'Pris - Siden du har en eller flere varianter må prisen bli satt under "Variasjon" fanen. ' + Product_OrderItem: + PLURALNAME: Produkter + SINGULARNAME: Produkt + db_ProductVersion: 'Produktversjon' + has_one_Product: Produkt + RegionRestriction: + PLURALNAME: 'Regionale restriksjoner' + SINGULARNAME: 'Regional restriksjon' + db_City: By + db_Country: Land + db_PostalCode: 'Postkode' + db_State: Fylke + SetLocationForm: + ChooseCountry: 'Velg land...' + Country: Land ShippingModifier: - SINGULAR: Frakt + PLURALNAME: Modifikatorer + SINGULARNAME: Frakt + Shop: + Change: Endre + Customer: Kunde + Date: Dato + DateFormatNice: '%m/%d/%G' + DateTimeFormatNice: '%m/%d/%G %I:%M%p' + DateTimeFormatNice24: 'd/m/Y H:i' + DevToolsTitle: 'Vis SilverShop utviklingsverktøy' + Edit: Endre + Quantity: Antall + ReadMoreTitle: 'Klikk her for å lese mer om "{Title}"' + Remove: Fjern + View: vis + ShopConfig: + AllowedCountries: 'Gi tilgang til sortering og fraktland.' + AllowedCountriesTabTitle: 'Tillate land' + CustomerGroup: 'Gruppe for å legge nye kunder i' + DefaultImage: 'Standard produktbilde' + TermsPage: 'Vilkår og betalingsbetingelser side' + ShopCurrency: + Free: 'Gratis!' + ShopDashboard: + NumberOfAccounts: 'Antall konti å vise' + NumberOfDays: 'Antall dager å vise' + NumberOfOrders: 'Antall ordrer å vise' + RecentAccounts: 'Siste konti' + RecentAccountsDescription: 'Vis siste konti' + RecentOrders: 'Siste ordrer' + RecentOrdersChart: 'Ordrehistorie' + RecentOrdersChartDescription: 'Vi siste ordrer som graf' + RecentOrdersDescription: 'Vis siste ordrer' + ShopDevelopmentAdmin: + BuildTasks: 'Byggeoppgaver' + CartCleanup: 'Rengjør gamle handlekurver' + CartCleanupDesc: 'Fjern forlatte handlekurver' + CartTasks: 'Handlekurvoppgaver' + ClearCart: 'Fjern nåværende handlekurv' + DebugCart: 'Debug handlekurven' + DeleteOrders: 'Slett alle ordrer' + DeleteOrdersDesc: 'Fjern alle ordre, modifikatorer og betalinger fra databasen.' + DeleteProducts: 'Fjern alle produkt' + DeleteProductsDesc: 'Fjern alle produkt fra databasen.' + PopulateCart: 'Bygg handlekurv men noen produkter' + PopulateShop: 'Bygg SilverShop' + PopulateShopDesc: 'Bygg SilverShop med ''dummy''-produkter, -kategorier og andre nødvendige sider.' + RecalculateOrders: 'Kalkuler alle ordre på ny.' + RecalculateOrdersDesc: 'Kalkuler alle ordreverdier. Obs: dette vi skrive over alle verdier og kan ikke bli omgjort!!' + RunAllTests: 'Kjør alle SilverShop unit tester' + UnitTests: 'Unit Tests' + ShopEmail: + AdminNotificationSubject: 'Ordre #{OrderNo} beskjed' + AdminNotificationTitle: 'Kvittering' + CancelSubject: 'Ordre #{OrderNo} kansellert av bruker' + ConfirmationSubject: 'Ordre #{OrderNo} stadfesting' + ConfirmationTitle: 'Ordrestadfesting' + ReceiptSubject: 'Ordre #{OrderNo} kvittering' + ReceiptTitle: 'Kvittering' + StatusChangeTitle: 'Statusendring' + StatusChanged: 'Status for ordre #{OrderNo} er endret til "{OrderStatus}"' + ShopPeriodReport: + Description: 'Perioderapport' + Title: 'Perioderapport' + ShopQuantityField: + AddOne: 'Legg "{ItemTitle}" til handlekurven din' + RemoveOne: 'Fjern "{ItemTitle}" fra handlekurven din' + ShopSalesReport: + Description: 'Følg butikksalg ytelse for en periode. Filtere resultatet på år, måned eller dag.' + Title: 'Salg' + ShopSideReport: + AllProducts: 'Alle produkter' + FeaturedProducts: 'Fremhevet produkt' + Heavy: 'Tunge produkt' + NoImage: 'Produkt uten bilde' + ShopGroup: SilverShop + ShoppingCart: + CannotPurchase: '{Title} er utilgjengelig for kjøp.' + Checkout: Kasse + Cleared: 'Handlekurv ble tømt.' + ContinueShopping: 'Fortsett å handle' + Headline: 'Handlekurv' + InvalidSecurityToken: 'Feil sikkerhetstoken, et mulig CSRF angrep.' + ItemAdded: 'Produkt ble lagt til.' + ItemNotFound: 'Finner ikke produkt.' + ItemRemoved: 'Produkt ble fjernet.' + ItemsInCartPlural: 'Det er {quantity} produkt i handlekurven' + ItemsInCartSingular: 'Det er 1 produkt i handlekurven.' + NoCartFound: 'Handlekurv finnes ikke.' + NoCartInitialised: 'ingen handlekurv ble startet' + NoItems: 'Handlekurven er tom.' + NoOrder: 'Ingen ordrer.' + ProceedToCheckout: 'Fortsett til kasse' + ProductNotFound: 'Produkt finnes ikke.' + QuantitySet: 'Antall må ha en verdi.' + RemoveAllTitle: 'Fjern alle "{Title}" fra handlekurven' + RemoveTitle: 'Fjern "{Title}" fra handlekurven.' + TableSummary: 'Din handlekurv inneholder' SimpleShippingModifier: - SHIPTO: "Send til %s" + PLURALNAME: 'Enkel fraktmodifikator.' + SINGULARNAME: Frakt + ShipToCountry: 'Send til (Land)' + db_Country: Land + SubTotalModifier: + PLURALNAME: 'Sum' + SINGULARNAME: 'Sum' + TaxModifier: + AtRate: '@ {Rate}%' + PLURALNAME: Moms + SINGULARNAME: Moms + db_Rate: Sats + TaxReport: + Description: 'Momsrapport på belastede ordrer. Inkluderer bare ordre som har status betalt.' + Title: Moms + Variation: + SINGULARNAME: Variant + VariationForm: + ChooseAttribute: 'Velg {attribute} ...' + ProductNotAvailable: 'Dette produktet er ikke tilgjengelig med dette valget.' + VariationNotAvailable: 'Denne varianten er ikke tilgjengelig.' WeightShippingModifier: - TABLETITLE: "Frakt (%f kg)" + PLURALNAME: 'Vekt fraktmodifikator.' + SINGULARNAME: Frakt + TableTitle: 'Frakt ({Kilograms} kg)' + Zone: + PLURALNAME: Soner + SINGULARNAME: Soner + db_Description: Beskrivelse + db_Name: Navn + has_many_Regions: Regioner + ZoneRegion: + PLURALNAME: 'Soneregioner' + SINGULARNAME: 'Soneregion' + has_one_Zone: Sone diff --git a/lang/nn.yml b/lang/nn.yml index aaad168b9..040e502c0 100644 --- a/lang/nn.yml +++ b/lang/nn.yml @@ -1,95 +1,492 @@ ---- nn: - AccountNavigation: - AddressBook: Adressebok - EditProfile: "Endre profil" - LogOut: "Logg ut" - MemberEmail: Epost - MemberLastVisit: "Siste besøk" - MemberName: Namn - MemberSince: "Medlem sida" - NumberOfOrders: "Antal bestillingar" - PastOrders: "Ordre arkiv" - Title: "Min konto" + OrdersAdmin: + MENUTITLE: Ordrar + ProductCatalogAdmin: + MENUTITLE: Katalog + ZoneAdmin: + MENUTITLE: Sonar + AbandonedCartReport: + Description: 'Overvak isolerte handlekorger for ein bestemt periode. Grupper resultata for eit år, månad eller dag.' + Title: 'Ufullstendige handlekorger' AccountPage: - NoPastOrders: "Det finnes ingen bestillingar." - Title: "Tidligere bestillingar" - AccountPage.ss: - ORDER: Bestilling + AddressBook: 'Adressebok' + DESCRIPTION: 'Gi brukar tilgang til å sjå sine kontodetaljer og ordrar.' + DefaultTitle: Konto + EditProfile: 'Rediger profil' + LogOut: 'Logg ut' + Login: "Du må vere logga inn for å få tilgang til kontoen. Om du ikkje er registrert vil du få tilbod om det når bestiller din første ordre." + LoginAgain: 'Du er blitt logga ut. Logg inn igjen om du vil!' + MemberEmail: E-post + MemberLastVisit: 'Siste besøk' + MemberName: Namn + MemberSince: 'Medlem sidan' + NoPage: 'Inga kontoside blei funne. Venleg lag ei i adminpanelet.' + NoPastOrders: 'Inga ordrar blei funne' + NumberOfOrders: 'Mengde av ordrar' + PLURALNAME: 'Kontoside' + PastOrders: 'Gamle ordrar' + SINGULARNAME: 'Kontoside' + Title: 'Min konto' AccountPage_AddressBook: - CreateNewTitle: "Opprett ny adresse" - NoAddress: "Det finnast inga adresse." - Title: Adresser + CreateNewTitle: 'Lag ei ny adresse' + NoAddress: 'Inga adresse blei funne' + Title: 'Standardadresse' + DefaultShippingAddress: 'Standard fraktadresse' + DefaultBillingAddress: 'Standard fakturaadresse' + MakeDefaultShipping: 'Gjer den til standard frakt' + MakeDefaultShippingTitle: 'Gjer denne til standard fraktadresse' + MakeDefaultBilling: 'Gjer denne til standard betaling' + MakeDefaultBillingTitle: 'Gjer denne til standard betalingadresse' + DeleteAddress: 'Slett adressa' AccountPage_EditProfile: - Title: "Endre profil" + Title: 'Rediger profil' Address: - ADDRESS: Adresse - ADDRESS2HINT: "bygning, leiligher, etasje, c.o." - ADDRESSHINT: "gatenavn, postboks" - ADDRESSLINE2: "Adresse 2 (valfri)" - BillingAddress: Fakturaadresse - CITY: Sted - CITYHINT: "eller tettsted, bygg, by" - COUNTRY: Land - PHONE: Telefon - POSTALCODE: Postnummer - STATE: Fylke - STATEHINT: "eller øy, provins" - SaveDefaults: "Lagre addressar" - SaveNew: "Lagre ny adresse" - ShippingAddress: Postadresse + AddressHint: 'gate / nummer, namn og type eller postboks ' + AddressLine2Hint: 'byggning, leilighet, etasje' + BillingAddress: 'Betalingsadresse' + CityHint: 'sted, kommune' + CreateNewAddress: 'Lag ny adresse' + ExistingBillingAddress: 'Eksisterande betalingsadresse' + ExistingShippingAddress: 'Eksisterande fraktadresse' + PLURALNAME: Adresser + SINGULARNAME: Adresse + SaveDefaults: 'Lagre som standard' + SaveNew: 'Lagre ny adresse' + ShippingAddress: 'Fraktadresse' + StateHint: 'eller provins, område, øy' + db_Address: Adresse + db_AddressLine2: 'Adresse linje 2 (valfri) ' + db_City: By + db_Company: Bedrift + db_Country: Land + db_FirstName: 'Fornamn' + db_Phone: 'Telefonnummer' + db_PostalCode: 'Postnummer' + db_State: Fylke + db_Surname: Etternamn + has_one_Member: Medlem + CartForm: + REMOVED_ITEMS: 'Fjerna {count} produkt.' + UPDATED_ITEMS: 'Oppdatert {count} produkt.' + UpdateCart: 'Oppdater handlekorg' + CartPage: + DESCRIPTION: 'Ein side kor ein kunde kan sjå og redigere innhaldet i handlekorga sin.' + DefaultTitle: 'Handlekorg' + PLURALNAME: 'Handlekorgsider' + SINGULARNAME: 'Handlekorgside' + has_one_CheckoutPage: 'Kasseside' + has_one_ContinuePage: 'Forsettside' + Checkout: + IdFieldNotFound: 'Obligatorisk felt er ikkje funnet: {IdentifierField}' + MemberExists: 'Et medlem finnes allereie med {Field} {Identifier}' + MembershipIsNotAllowed: 'Det stengt for å lage ny konto' + NoPaymentMethod: 'Det er nødvendig med eit passord' + PasswordRequired: 'Eit passord er nødvendig' + TermsAndConditionsLink: 'Eg godtar salvilkår {TermsPageTitle} page' + CheckoutComponentValidator: + InvalidDataMessage: 'Noko av dataen du har skrevet er feil, se under:' + CheckoutField: + AccountInfo: 'Ver venleg og vel eit passord, du kan seinare logge inn og sjå ordrehistorikken din' + MemberLoginInfo: 'Om allereie er medlem ver venleg og logg inn' + MembershipDetails: 'Medlemskapdetaljer' + MustAgreeToTerms: 'Du må godta vilkåra' + Password: Passord + PaymentType: 'Betalingsmåte ' + CheckoutPage: + DESCRIPTION: 'Side som handterar kassefunksjonen ' + DefaultTitle: Kasse + PLURALNAME: 'Kassesider' + PaymentErrorMessage: 'Ein feil skjedde ved betaling, error frå betaling gateway:' + ProceedToPayment: 'Fortsett til betaling' + PurchaseCompleteDescription: 'Denne beskjeden blir inkludert i kvittering som blir sendt på epost etter at ordren er betalt.' + SINGULARNAME: 'Kasseside' + SubmitPayment: 'Send betaling' + db_PurchaseComplete: 'Ordre er betalt' + CheckoutStep: + Continue: Fortsett + Shipping: Frakt + Summary: Oppsummering + CheckoutStep_Address: + BillTo: 'Send rekning til:' + EnterShippingAddress: 'Ver venleg og skriv inn din fraktadresse' + SeperateBilling: 'Send rekning til ein annen adresse' + ShipTo: 'Send sending til:' + SupplyContactInformation: 'Skriv inn din kontakt informasjon' + CheckoutStep_Membership: + ContinueAsGuest: 'Hald fram som gjest' + CreateAccount: 'Lag ein konto' + CreateNewAccount: 'Lag ein ny konto' + CreateAddressForm: + AddressSaved: 'Di adresse er blitt lagra' + CustomerReport: + Description: 'Kunden sin rapport' + Title: Kunden + FlatTaxModifier: + PLURALNAME: Momsen + SINGULARNAME: Moms FreeShippingModifier: - FREE: Gratis - Member: - BUTTONCHANGEPASSWORD: "Endre passord" - CONFIRMNEWPASSWORD: "Bekreft nytt passord" - DATEFORMAT: Datoformat - EMAIL: Epost - FIRSTNAME: Navn - NEWPASSWORD: "Nytt passord" - SURNAME: Etternavn - TIMEFORMAT: Tidsformat - YOUROLDPASSWORD: "Ditt gamle passord" - db_Locale: Språk + Free: Gratis + PLURALNAME: 'Gratis fraktmodifikator' + SINGULARNAME: Frakt + GlobalTaxModifier: + Included: '(ikkje inkluder i prisen ovanfor)' + PLURALNAME: Momsen + SINGULARNAME: Moms + db_Country: Land MemberForm: - DETAILSSAVED: "Dine endringer er lagret" - SAVE: "Lagre endringer" - SAVEANDPROCEED: "Lagre og gå til betaling" + DetailsSaved: 'Din detaljer har blitt lagra' + Save: 'Lagre endringar' + SaveAndProceed: 'Lagre og fortset til kassa' + OnsitePaymentCheckoutComponent: + InvalidCreditCard: 'Kredittkort er ugyldig' Order: - ORDERNOTES: Note - TOTALOUTSTANDING: "Utestående beløp" - OrderHistory: - OrderDate: Dato - OrderItems: Antall - OrderReference: Referanse - OrderStatus: Status - OrderTotal: Total - ViewOrder: "vis bestilling" - Order_Address.ss: - BILLTO: Fakturaadresse - SHIPTO: Fraktadresse - Order_Content.ss: - PRODUCT: Produkt - QUANTITY: Antall - TOTALPRICE: Total - UNITPRICE: Pris - Order_Content_ItemLine.ss: - READMORE: "Sjå meir om \\\"%s\\\"" - Order_Content_SubTotals.ss: - SUBTOTAL: "Total ink mva" - TOTAL: Total - Order_Payments.ss: - AMOUNT: Sum - DATE: Dato - PAYMENTMETHOD: Betalingsmåte - PAYMENTNOTE: Melding - PAYMENTS: Betalingar - PAYMENTSTATUS: Status + BillTo: 'Send rekning til' + Date: Dato + DateFrom: 'Dato frå' + DateTo: 'Dato til' + GrandTotal: 'Pris total' + Invoice: Faktura + OrderHeadline: 'Ordre #{OrderNo} {OrderDate}' + Outstanding: Uteståande + OutstandingWithAmount: 'Uteståande: {Amount}' + PLURALNAME: Ordrar + Print: Print + Quantity: Mengde + SINGULARNAME: 'Ordre ' + STATUS_ADMINCANCELLED: 'Avlyst av admin' + STATUS_CART: Handlekorg + STATUS_COMPLETE: Ferdig + STATUS_MEMBERCANCELLED: 'Avlyst av kunde' + STATUS_PAID: Betalt + STATUS_PROCESSING: Under behandling + STATUS_SENT: Sendt + STATUS_UNPAID: Ubetalt + ShipTo: 'Send sending til:' + SubTotal: Sum total + Total: Total + TotalOutstanding: 'Total uteståande' + TotalPrice: 'Total pris' + TotalPriceWithCurrency: 'Total pris ({Currency})' + UnitPrice: 'Eining pris' + db_Dispatched: Sending sendt + db_Email: E-post + db_FirstName: 'Fornamn' + db_IPAddress: 'IP-adresse' + db_Notes: Beskjed + db_Paid: Betalt + db_Placed: Plassert + db_Printed: Printa + db_ReceiptSent: 'Kvittering sendt' + db_Reference: Referanse + db_SeparateBillingAddress: 'Eiga betalingsadresse' + db_Status: Status + db_Surname: Etternamn + db_Total: Total + has_many_Items: Produkt + has_many_Modifiers: Modifikatorar + has_many_OrderStatusLogs: 'Ordrestatuslogg' + has_many_Payments: Betalingar + has_one_BillingAddress: 'Betalingsadresse' + has_one_Member: Medlem + has_one_ShippingAddress: 'Fraktadresse' + OrderActionsForm: + CancelOrder: 'Avbestill ordren' + MakePayment: 'Betal' + ManualNotAllowed: 'Manuell betaling er ikkje tillate' + PayOrder: 'Betal uteståande balanse' + PaymentMethod: 'Betalingsmåte' + OrderAdmin: + ReceiptTitle: '{SiteTitle} Ordre {OrderNo}' + OrderAttribute: + PLURALNAME: Atributter + SINGULARNAME: Atribut + db_CalculatedTotal: 'Kalkulert total' + has_one_Order: 'Ordre ' + OrderForm: + CouldNotProcessPayment: 'Betaling kunne ikkje bli gjennomført' + OrderCancelled: 'Ordre er avbestilt' + OrderItem: + PLURALNAME: Produkt + SINGULARNAME: Produkt + db_Quantity: Mengde + db_UnitPrice: 'Eining pris' + OrderModifier: + PLURALNAME: Modifikatorar + SINGULARNAME: Modifikator + db_Amount: Beløp + db_Sort: Sortere + db_Type: Type + OrderProcessor: + NoItems: 'Ordre har ingen produkt' + NoOrder: 'Ordre finnast ikkje.' + NoOrderStarted: 'Den nye ordren har ikkje begynt ennå' + NotCart: 'Ordre er ikkje ei handlekorg.' + OrderStatusLog: + PLURALNAME: 'Ordrestatuslogglinjer' + SINGULARNAME: 'Ordrelogglinje' + db_DispatchTicket: 'Send billett' + db_DispatchedBy: 'Sendt av' + db_DispatchedOn: 'Sendt den' + db_Note: Melding + db_PaymentCode: 'Betalingskode ' + db_PaymentOK: 'Betaling OK' + db_SentToCustomer: 'Send til kunde' + db_Title: Tittle + has_one_Author: Forfattar + has_one_Order: Ordre + Payment: + Note: Melding + PaymentsHeadline: Betaling(ar) + PaymentCheckoutComponent: + NoPaymentMethod: 'Betalingsmåte er ikkje gitt' + UnsupportedGateway: 'Gateway er ikkje støtta' + PaymentProcessor: + CantPay: "Ordre kan ikkje betalast" + InvalidGateway: "`{gateway}` er ikkje ein gyldig betalinggateway" PickupShippingModifier: - SINGULAR: "Hent i butikk" + PLURALNAME: Modifikatorar + SINGULARNAME: 'Hent i butikk' + PriceTag: + SAVE: Lagre + Product: + AddToCart: 'Legg til handlekorg' + AddToCartTitle: 'Legg til "{Title}" handlekorga' + AdditionalCategories: 'Liknande kategoriar' + AllowPurchase: 'Tillat at produkt kan kjøpast' + Category: Kategori + CategoryDescription: 'Dette er ei foreldreside eller standardkategori.' + Code: 'Produktkode' + CostPriceDescription: 'Rettleiande pris, leverandørpris eller pris før varen har blitt rabattert.' + DESCRIPTION: 'Produktside i butikken' + Featured: 'Framheva produkt' + Image: 'Produktbilete' + ImageAltText: '{Title} bilete' + InternalItemID: 'Produktkode/SKU' + Model: Modell + NoImage: 'ingen bilete' + PLURALNAME: Produkt + PageTitle: 'Produkttittel' + Price: Pris + PriceDesc: 'Prisen varen blir seld for (salspris).' + ProductCodeShort: SKU + SINGULARNAME: Produkt + Size: Storleik + View: 'Vis produkt' + db_AllowPurchase: 'Tillat kjøp' + db_BasePrice: Pris + db_CostPrice: 'Rettleiande pris' + db_Depth: 'Djupn ' + db_Featured: Fremheva + db_Height: Høgde + db_InternalItemID: 'Intern-ID' + db_Model: Modell + db_Popularity: Popularitet + db_Weight: Vekt + db_Width: ' Breidde' + has_many_Variations: Variasjon + has_one_Image: Bilete + many_many_ProductCategories: 'Produktkategoriar' + many_many_VariationAttributeTypes: 'Variasjonattributttype' + ProductAttributeType: + PLURALNAME: Atributter + SINGULARNAME: Atribut + SaveFirstInfo: 'Du må lagre først, så du kan legge til verdiar' + belongs_many_many_Product: Produkt + db_Label: Merkjelapp + db_Name: Namn + has_many_Values: Verdiar + ProductAttributeValue: + PLURALNAME: Verdiar + SINGULARNAME: Verdi + belongs_many_many_ProductVariation: 'Produktvariasjon' + db_Sort: Om rokere + db_Value: Verdi + has_one_Type: Type + ProductCategory: + DESCRIPTION: 'Produktkategori i butikken' + PLURALNAME: Kategoriar + SINGULARNAME: Kategori + belongs_many_many_Products: Produkt + ProductGroup: + Next: neste + Page: Side + Previous: forrige + ViewNext: 'Vis neste side' + ViewPage: 'Vis side {pageNum}' + ViewPrevious: 'Vis forrige side' + ProductMenu: + GotoPageTitle: 'Gå til {Title}' + ProductReport: + Description: "Forstå kva for eit produkt som gjer det bra og ikkje" + Title: Produkt + ProductVariation: + MustSaveFirstMessage: 'Du kan velje ein variant-attributt etter du har lagra for første gang, om det finnes ein. ' + NoAttributeValuesMessage: '{attribute} har ingen verdi. Du kan lage dem hos "Products" > "Product Attribute Type" seksjonen i adminpanelet.' + PLURALNAME: Variasjon + SINGULARNAME: Variasjon + db_InternalItemID: 'Intern-ID' + db_Price: Pris + db_Version: Versjon + has_one_Image: Bilete + has_one_Product: Produkt + many_many_AttributeValues: 'Attributtverdi' + ProductVariation_OrderItem: + PLURALNAME: Produkt + SINGULARNAME: Produkt + db_ProductVariationVersion: 'Produktvariasjonversjon' + has_one_ProductVariation: 'Produktvariasjon' + ProductVariationsExtension: + Attributes: Attributt + AttributesDescription: 'Disse feltane viser måten kvar variasjon varierer. Etter at ein av de er valen, kan man redigere kvar variasjon.' + Variations: Variasjon + VariationsInfo: 'Pris - Siden du har ein eller fleire variantar må prisen bli sette under "Variasjon" fanen. ' + Product_OrderItem: + PLURALNAME: Produkt + SINGULARNAME: Produkt + db_ProductVersion: 'Produktversjon' + has_one_Product: Produkt + RegionRestriction: + PLURALNAME: 'Regionale restriksjoner' + SINGULARNAME: 'Regional restriksjon' + db_City: By + db_Country: Land + db_PostalCode: 'Postnummer' + db_State: Fylke + SetLocationForm: + ChooseCountry: 'Vel land...' + Country: Land ShippingModifier: - SINGULAR: Frakt + PLURALNAME: Modifikatorar + SINGULARNAME: Frakt + Shop: + Change: Endre + Customer: Kunde + Date: Dato + DateFormatNice: '%m/%d/%G' + DateTimeFormatNice: '%m/%d/%G %I:%M%p' + DateTimeFormatNice24: 'd/m/Y H:i' + DevToolsTitle: 'Vis utviklingsverktøy' + Edit: Endre + Quantity: Antall + ReadMoreTitle: 'Klikk her for å lese meir om "{Title}"' + Remove: Fjern + View: vis + ShopConfig: + AllowedCountries: 'Gi tilgang til sortering og fraktland.' + AllowedCountriesTabTitle: 'Tillate land' + CustomerGroup: 'Gruppe for å legge nye kundar i' + DefaultImage: 'Standard produktbilete' + TermsPage: 'Vilkårside' + ShopCurrency: + Free: 'Gratis' + ShopDashboard: + NumberOfAccounts: 'Talet konti å vise' + NumberOfDays: 'Talet dagar å vise' + NumberOfOrders: 'Talet ordre' + RecentAccounts: 'Siste konti' + RecentAccountsDescription: 'Vis siste konti' + RecentOrders: 'Siste ordrar' + RecentOrdersChart: 'Ordrehistorie' + RecentOrdersChartDescription: 'Vi siste ordrar som graf' + RecentOrdersDescription: 'Vis siste ordrar' + ShopDevelopmentAdmin: + BuildTasks: 'Byggeoppgavar' + CartCleanup: 'Reingjør gamle handlekorger' + CartCleanupDesc: 'Fjern forlatne handlekorger' + CartTasks: 'Handlekorgoppgåver' + ClearCart: 'Fjern noverande handlekorg' + DebugCart: 'Debug handlekorga' + DeleteOrders: 'Slett alle ordrer' + DeleteOrdersDesc: 'Fjern alle ordre, modifikatorer og betalingar frå databasen.' + DeleteProducts: 'Slett alle produkt' + DeleteProductsDesc: 'Slett alle produkt frå databasen.' + PopulateCart: 'Bygg butikk med ''dummy''-produkt, -kategoriar og andre nødvendige sider.' + PopulateShop: 'Bygg butikk' + PopulateShopDesc: 'Bygg butikk med ''dummy''-produkt, -kategoriar og andre nødvendige sider.' + RecalculateOrders: 'Kalkuler alle ordre på ny.' + RecalculateOrdersDesc: 'Kalkuler alle ordreverdiar. Obs: dette vi skrive over alle verdiar og kan ikkje bli omgjort!!' + RunAllTests: 'Kjør alle SilverShop unit tester' + UnitTests: 'Unit Tests' + ShopEmail: + AdminNotificationSubject: 'Ordre #{OrderNo} beskjed' + AdminNotificationTitle: 'Butikkvittering' + CancelSubject: 'Ordre #{OrderNo} kansellert av bruker' + ConfirmationSubject: 'Ordre #{OrderNo} bekreftelse' + ConfirmationTitle: 'Ordrebekreftelse' + ReceiptSubject: 'Ordre #{OrderNo} kvittering' + ReceiptTitle: 'Kvittering' + StatusChangeTitle: 'Statusendring' + StatusChanged: 'Status for ordre #{OrderNo} er endra til "{OrderStatus}"' + ShopPeriodReport: + Description: 'Perioderapport' + Title: 'Perioderapport' + ShopQuantityField: + AddOne: 'Legg "{ItemTitle}" til handlekorga di' + RemoveOne: 'Fjern "{ItemTitle}" fra handlekorga di' + ShopSalesReport: + Description: 'Fylg butikksal yting for ein periode. Filter resultatet på år, månad eller dag.' + Title: 'Sal' + ShopSideReport: + AllProducts: 'Alle produkt' + FeaturedProducts: 'Framheva produkt' + Heavy: 'Tunge produkt' + NoImage: 'Produkt uten bilete' + ShopGroup: SilverShop + ShoppingCart: + CannotPurchase: '{Title} er utilgjengelig for kjøp.' + Checkout: Kasse + Cleared: 'Handlekorg ble tømt.' + ContinueShopping: 'Fortsett å handle' + Headline: 'Handlekorg' + InvalidSecurityToken: 'Feil sikkerhetstoken, eit mulig CSRF angrep.' + ItemAdded: 'Produkt ble lagt til.' + ItemNotFound: 'Finn ikkje produkt.' + ItemRemoved: 'Produkt ble fjerna.' + ItemsInCartPlural: 'Det er {quantity} produkt i handlekorga' + ItemsInCartSingular: 'Det er 1 produkt i handlekorga.' + NoCartFound: 'Inga handlekorg blei funne' + NoCartInitialised: 'ingen handlekorg blei starta' + NoItems: 'Handlekorga er tom.' + NoOrder: 'Inga ordrar.' + ProceedToCheckout: 'Fortsett til kasse' + ProductNotFound: 'Produkt finnes ikkje.' + QuantitySet: 'Talet må ha ein verdi.' + RemoveAllTitle: 'Fjern alle "{Title}" fra handlekorga' + RemoveTitle: 'Fjern "{Title}" fra handlekorga' + TableSummary: 'Di handlekorg inneheld' SimpleShippingModifier: - SHIPTO: "Send til %s" + PLURALNAME: 'Enkel fraktmodifikator.' + SINGULARNAME: Frakt + ShipToCountry: 'Send til (Land)' + db_Country: Land + SubTotalModifier: + PLURALNAME: 'Sum total' + SINGULARNAME: 'Sum total' + TaxModifier: + AtRate: '@ {Rate}%' + PLURALNAME: Momsen + SINGULARNAME: Moms + db_Rate: Sats + TaxReport: + Description: 'Momsrapport på belasta ordrar. Inkluderer bare ordre som har status betalt.' + Title: Moms + Variation: + SINGULARNAME: Variasjon + VariationForm: + ChooseAttribute: 'Vel {attribute} …' + ProductNotAvailable: 'Dette produktet er ikkje tilgjengelig med dette valet.' + VariationNotAvailable: 'Denne varianten er ikkje tilgjengelig.' WeightShippingModifier: - TABLETITLE: "Frakt (%f kg)" + PLURALNAME: 'Vekt fraktmodifikator.' + SINGULARNAME: Frakt + TableTitle: 'Frakt ({Kilograms} kg)' + Zone: + PLURALNAME: Sonar + SINGULARNAME: Sonar + db_Description: 'Beskriving ' + db_Name: Namn + has_many_Regions: Region + ZoneRegion: + PLURALNAME: 'Soneregionar' + SINGULARNAME: 'Soneregion' + has_one_Zone: Sonar diff --git a/templates/Includes/AccountNavigation.ss b/templates/Includes/AccountNavigation.ss index 6a57bdbf3..8e0712313 100644 --- a/templates/Includes/AccountNavigation.ss +++ b/templates/Includes/AccountNavigation.ss @@ -2,31 +2,31 @@