mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-09-22 10:45:03 -04:00
* fix(security): handle special characters in `.env` key values and improve insertion logic - Escape backslashes and dollar signs in `applyEnvKeyReplacement` to prevent unintended value corruption. - Ensure new keys are inserted after `encryption.key` for better organization and manageability. - Add explicit cast to int to prevent wrong concatenation operator warning. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): handle null return in `applyEnvKeyReplacement` and ensure proper `.env` updates - Update `applyEnvKeyReplacement` to return `null` on failure, improving error handling. - Adjust calls to `atomicWriteFile` with updated content to prevent unintended behavior. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): improve error logging and exception messages in file locking - Add detailed logging for file open and locking errors in `security_helper`. - Remove unused `helper` and `checkThrottleEncryption` calls from `Events` for cleanup. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): improve atomic file write and handle encryption key placement - Throw `RandomException` for better error reporting in `atomicWriteFile`. - Simplify Windows-specific `rename()` fallback logic. - Fix `encryption.key` assignment order to ensure consistency in `.env` updates. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): improve `.env` file handling and add unit tests for helper functions - Suppress warnings in `file_get_contents` to prevent unnecessary error logs. - Update `applyEnvKeyReplacement` to use `preg_replace_callback` for better safety. - Add comprehensive unit tests for `security_helper` functions to ensure `.env` updates and key management work as expected. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): enhance `.env` update logic and add robust exception handling - Add `RandomException` to improve error reporting in encryption key management. - Introduce environment file locking for safer `.env` updates. - Ensure `applyEnvKeyReplacement` properly handles and inserts old key comments. - Replace direct file writes with `atomicWriteFile` for consistency. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): refactor `.env` file initialization and encryption key handling - Introduce `initializeEnvFile` for reusable `.env` setup logic. - Add `backupEnvFile` and `writeNewEncryptionKey` for robust key management with backups. - Simplify and clean up redundant `.env` handling code paths. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): clarify `checkEncryption` docblock return value description Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(security): escape backslashes and dollar signs in `applyEnvKeyReplacement` - Ensure `applyEnvKeyReplacement` properly escapes special characters when inserting or appending `.env` keys. - Add new unit tests to validate correct handling of backslashes and dollar signs. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(i18n): add localized error messages and improve error reporting in `security_helper` - Add missing translations for error messages across multiple language files. - Update `security_helper` to use localized exception messages with placeholders. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * Redesign encryption/throttle key provisioning as read-only runtime - checkEncryption()/checkThrottleEncryption() are now read-only guards that throw when no valid key is provisioned, instead of writing .env at request time. - Add rotateEncryptionKey() and provisionThrottleKey() for explicit, idempotent provisioning. - Add php spark env:provision (app/Commands/EnvProvision.php) so Docker can provision keys once at container startup before any request. - Add app/Libraries/CI3SecretConverter.php shared CI3->CI4 secret converter (AES-128-CBC decrypt + CI4 re-encrypt/verify/save) used by both the interactive migration and the docker startup path. - Refactor convertToCI4 migration to use the shared converter. - Persist .env in a named volume and run spark env:provision on boot; stop baking .env into the shipped image. - Add guard/rotation/throttle + converter tests; clean up orphaned msg_pwd_required language keys across all locales. * fix: save CI4 ciphertext in env:provision and bind-mount a .env file Addresses CodeRabbit review on PR #4656: - env:provision CI3 branch was persisting *plaintext* secrets (saveAll($plain)) instead of the CI4 ciphertext, unlike the ConvertToCI4 migration. Now encrypts with encryptAll(), verifies the round trip, and saves the ciphertext. - The ospos_env named volume mounted at /app/.env made .env a directory, so atomicWriteFile's rename() failed and spark env:provision could not start apache. Switch to a bind mount of a host file (./.env) which persists and stays a file. - Add a regression test asserting the command persists ciphertext (not plaintext). * chore: trim redundant docblocks in EnvProvision and provision throttle.key in CI Follow up on @objecttothis review comments: - app/Commands/EnvProvision.php: remove the boilerplate docblocks the property names already convey (group/name/usage/description, run()), the two inline step comments, the anyNonEmpty() param docblock, and the legacySecretsPresent() docblock. Keeps the class-level docblock since it is the only place that states the read-only runtime design + the never-persist-plaintext invariant. - .github/workflows/phpunit.yml: provision a per-run throttle.key the same way the encryption key is already provisioned. The PR makes checkThrottleEncryption() a read-only guard that throws when env('throttle.key') is unset; CI only started exporting ENCRYPTION_KEY, so every test that goes through the Throttle filter (7 ThrottleTest cases + 4 LoginTest cases) failed with "No throttle key is provisioned. Run `php spark env:provision`". Writing `throttle.key=<KEY>` into .env matches what `php spark env:provision` does on a real container start. * fix(ci): write throttle.key into .env instead of exporting an OS env var The previous attempt exported throttle.key via GITHUB_ENV, but CodeIgniter's env() helper resolves in the order $_ENV[$key] ?? $_SERVER[$key] ?? getenv($key), and DotEnv populates $_ENV['throttle.key'] from the .env file first. Because the .env (copied from .env.example) ships with the empty placeholder throttle.key='', that $_ENV entry exists as '' and short-circuits the ?? chain before getenv() is reached — so the OS env var was never consulted and every Throttle/Login test still threw 'No throttle key is provisioned'. Write the per-run key into the .env file itself (sed-replacing the empty placeholder), which is exactly what `php spark env:provision` does in production and is the single source env() actually reads from. Verify the replacement happened (grep -Eq '^throttle\.key=.') so a future change to the placeholder format fails the run loudly instead of silently breaking the 11 throttle-dependent tests. * fix(security): restore CI3->CI4 auto-provisioning gated by .env writability checkEncryption()/checkThrottleEncryption() again provision the keys inline when .env is writable (empty key -> generate; short key -> decrypt, rotate, re-encrypt, verify, persist legacy CI3 secrets). When .env is not writable they assume the key was provisioned externally (e.g. docker env:provision) and throw. Update helper tests to match and correct the EnvProvision docblock that claimed the runtime was strictly read-only. * test(security): make short-key conversion branch injectable and test it checkEncryption() now accepts an optional CI3SecretConverter so the CI3->CI4 conversion branch can be exercised in unit tests without a database. Adds testCheckEncryptionConvertsCi3ShortKeyWhenEnvWritable which seeds CI3-era ciphertexts via a fake Appconfig model and asserts the key is rotated and the payload verifies back to the original plaintext. * fix(security): abort on backup/read/saveAll failure to avoid data loss Three related data-integrity fixes: - backupEnvFile() now returns true/false based on whether the backup actually exists and is readable. rotateEncryptionKey() aborts before destroying the key when the backup could not be written to disk. - rotateEncryptionKey() and provisionThrottleKey() throw RuntimeException(Error.unable_to_read_env_file) when the .env read fails, instead of silently replacing the whole file with an empty string. This prevents a permission error from wiping all keys. - checkEncryption() and EnvProvision::run() now both roll back to the backup with abortEncryptionConversion() when the post-rotation saveAll() throws, matching the migration path (which already did this). A failing fake Appconfig is used to exercise this in the new testCheckEncryptionRollsBackWhenSaveAllFails test. * fix(ci): skip comment job in deploy-pr.yml when prepare was not run The comment job had if: always(), so it ran even when the prepare job was skipped (e.g. review was not approved). With PR_NUMBER empty the gh api call posted to issues//comments, received a 404, and the entire run showed up as failure. Guard the job with needs.prepare.result == 'success' so it only runs when PR_NUMBER is valid. * address coderabbit open items: placeholder guards, message neutrality, ar-EG alignment - backupEnvFile(): fail when mkdir() or either chmod() fails, so the pre-rotation backup is actually persisted before the key is replaced - email/message config views: only show the 'already set' placeholder when the secret is actually present (prevented false positives on fresh installs) - Error.unable_to_create_env_file / .unable_to_read_env_file (en + en-GB): use key-neutral wording since both keys are provisioned with the same keys - ar-EG/Error.php: align all => arrows on the longest key Item 7 (filesystem test isolation) is a larger refactor — the tests are serial on CI and tearDown() restores state per test. Left for follow-up. * test(security): isolate helper FS tests via Config\SecurityEnv Introduce Config\SecurityEnv holding envPath/backupPath/lockPath so the security helper reads its target paths from shared configuration instead of hardcoded ROOTPATH/WRITEPATH literals. security_helperTest.php now redirects all three to a unique per-run sandbox under sys_get_temp_dir() and tears it down in tearDown(), so the suite no longer reads/writes the repository's real .env and is safe to run in parallel. No helper signature changes; production callers unaffected. Addresses CodeRabbit item 7 (issue #4700). Co-Authored-By: opencode <bot@opencode.ai> * fix(security): run key-conversion as one locked transaction Address CodeRabbit Major findings from the 4th re-review of the env helper and its callers: 1. Hold .env.lock for the entire CI3 -> CI4 conversion transaction (backup -> rotate -> re-encrypt -> verify -> persist -> cleanup) so a concurrent worker cannot interleave a key write between the rotation and the ciphertext save. Split rotateEncryptionKey into a lock-free core (rotateEncryptionKeyUnlock) plus the existing lock wrapper and a new rotateEncryptionKeyTransaction that owns the lock across the full unit and performs both the in-lock rollback (abortEncryptionConversion) and the in-lock backup removal on success. 2. Treat the legacy value '0' as non-empty data so key rotation still persists the re-encrypted ciphertext when '0' is the only stored secret (array_filter would have dropped it and skipped saveAll). 3. Wrap the post-rotation re-encrypt/verify/saveAll sequence in a catch (Throwable) across all three call-sites so CI4 EncryptionException, ReflectionException from batch_save, a failed round-trip verify, and any other failure all roll the .env key back to the pre-rotation state. 4. In Docker Compose, use long-syntax bind with create_host_path: false and document in INSTALL.md that the host .env must be a regular file (a missing one is no longer auto-created as a directory, and the mount now rejects a missing source on Compose implementations that support the flag). Files touched: app/Helpers/security_helper.php, app/Commands/EnvProvision.php, app/Database/Migrations/20220127000000_convertToCI4.php, docker-compose.yml, INSTALL.md. All 4 existing helper tests still pass via CI. * fix(security): make abortEncryptionConversion fail loudly on restore failure The rollback path restored the .env backup with a suppressed file_put_contents() and an unchecked file_get_contents(). If the restore failed after the key had already been rotated, .env was left holding the new CI4 key while the DB still held CI3-era ciphertext, so the data became undecryptable after the next restart. Now the backup read is checked for false and the restore goes through the existing atomicWriteFile() helper; either failure throws so the error is surfaced instead of silently corrupting the config. Adds a regression test that forces an unreadable backup and asserts the throw plus that .env is left untouched. * fix(security): guard abortEncryptionConversion backup read before touching it Validate the backup is a regular readable file (is_file/is_readable) before reading it, so a missing/malformed backup fails loudly instead of emitting a file_get_contents() warning. The unreadable-backup regression test now exercises this guard rather than relying on a promoted warning. --------- Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> Co-authored-by: jekkos <jeroen.peelaerts@gmail.com> Co-authored-by: jekkos <jekkos@users.noreply.github.com> Co-authored-by: opencode <bot@opencode.ai>
335 lines
28 KiB
PHP
335 lines
28 KiB
PHP
<?php
|
|
|
|
return [
|
|
'address' => 'Địa chỉ công ty',
|
|
'address_required' => 'Trường địa chỉ công ty là bắt buộc.',
|
|
'all_set' => 'Mọi quyền đều được đặt chính xác!',
|
|
'allow_duplicate_barcodes' => 'Cho phép trùng Mã vạch',
|
|
'apostrophe' => 'dấu nháy đơn',
|
|
'backup_button' => 'Sao lưu',
|
|
'backup_database' => 'Sao lưu cơ sở dữ liệu',
|
|
'barcode' => 'Mã vạch',
|
|
'barcode_company' => 'Tên công ty',
|
|
'barcode_configuration' => 'Cấu hình Mã vạch',
|
|
'barcode_content' => 'Nội dung Mã vạch',
|
|
'barcode_first_row' => 'Dòng 1',
|
|
'barcode_font' => 'Phông chữ',
|
|
'barcode_formats' => 'Định dạng đầu vào',
|
|
'barcode_generate_if_empty' => 'Tự tạo mới nếu để trống.',
|
|
'barcode_height' => 'Cao (px)',
|
|
'barcode_id' => 'Tên/Mã hàng hóa',
|
|
'barcode_info' => 'Thông tin cấu hình Mã vạch',
|
|
'barcode_layout' => 'Bố cục Mã vạch',
|
|
'barcode_name' => 'Tên',
|
|
'barcode_number' => 'Mã vạch',
|
|
'barcode_number_in_row' => 'Số ở dòng',
|
|
'barcode_page_cellspacing' => 'Khoảng cách ô trang hiển thị.',
|
|
'barcode_page_width' => 'Chiều rộng trang hiển thị',
|
|
'barcode_price' => 'Giá',
|
|
'barcode_second_row' => 'Dòng 2',
|
|
'barcode_third_row' => 'Dòng 3',
|
|
'barcode_tooltip' => 'Cảnh báo: Tính năng này có thể là nguyên nhân làm trùng lặp hàng hóa khi nhập hay tạo. Đừng dùng nếu bạn không muốn trùng mã vạch.',
|
|
'barcode_type' => 'Kiểu Mã vạch',
|
|
'barcode_width' => 'Rộng (px)',
|
|
'bottom' => 'Đáy',
|
|
'cash_button' => '',
|
|
'cash_button_1' => '',
|
|
'cash_button_2' => '',
|
|
'cash_button_3' => '',
|
|
'cash_button_4' => '',
|
|
'cash_button_5' => '',
|
|
'cash_button_6' => '',
|
|
'cash_decimals' => 'Số chứ số sau dấy phẩy',
|
|
'cash_decimals_tooltip' => 'Nếu số lẻ thập phân của tiền mặt và tiền tệ là giống nhau thì sẽ không làm tròn.',
|
|
'cash_rounding' => 'Làm tròn số tiền',
|
|
'category_dropdown' => 'Hiển thị thể loại dạng hộp thả xuống',
|
|
'center' => 'Giữa',
|
|
'change_apperance_tooltip' => '',
|
|
'comma' => 'dấu phẩy',
|
|
'company' => 'Tên công ty',
|
|
'company_avatar' => '',
|
|
'company_change_image' => 'Đổi ảnh',
|
|
'company_logo' => 'Logo công ty',
|
|
'company_remove_image' => 'Gỡ bỏ ảnh',
|
|
'company_required' => 'Tên công ty là bắt buộc',
|
|
'company_select_image' => 'Chọn ảnh',
|
|
'company_website_url' => 'Website của công ty không hợp lệ (http://...).',
|
|
'country_codes' => 'Mã Nước',
|
|
'country_codes_tooltip' => 'Danh sách ngăn cách bằng dấu phẩy mã các nước cho tìm kiếm địa chỉ đề cử.',
|
|
'currency_code' => 'Mã tiền tệ',
|
|
'currency_decimals' => 'Số chữ số sau dấy phẩy',
|
|
'currency_symbol' => 'Ký hiệu tiền tệ',
|
|
'current_employee_only' => '',
|
|
'customer_reward' => 'Điểm thưởng',
|
|
'customer_reward_duplicate' => 'Điểm thưởng phải duy nhất.',
|
|
'customer_reward_enable' => 'Cho phép thưởng cho khách hàng',
|
|
'customer_reward_invalid_chars' => "Điểm thưởng không thể chứa '_'",
|
|
'customer_reward_required' => 'Điểm thưởng là trường bắt buộc',
|
|
'customer_sales_tax_support' => 'Hỗ trợ thuế bán hàng Khách hàng',
|
|
'date_or_time_format' => 'Bộ lọc ngày và giờ',
|
|
'datetimeformat' => 'Định dạng ngày và giờ',
|
|
'decimal_point' => 'Dấu thập phân',
|
|
'default_barcode_font_size_number' => 'Cỡ phông chữ Mã vạch mặc định phải là dạng số.',
|
|
'default_barcode_font_size_required' => 'Cỡ phông chữ Mã vạch mặc định là trường bắt buộc.',
|
|
'default_barcode_height_number' => 'Chiều cao Mã vạch mặc định phải là dạng số.',
|
|
'default_barcode_height_required' => 'Chiều cao Mã vạch mặc định là trường mặc định.',
|
|
'default_barcode_num_in_row_number' => 'Số Mã vạch trên một Hàng mặc định phải là dạng số.',
|
|
'default_barcode_num_in_row_required' => 'Số Mã vạch mặc định mỗi dòng tính bằng dòng là trường bắt buộc.',
|
|
'default_barcode_page_cellspacing_number' => 'Khoảng cách ô trang Mã vạch mặc định phải là dạng số.',
|
|
'default_barcode_page_cellspacing_required' => 'Khoảng cách ô trang Mã vạch mặc định là trường bắt buộc.',
|
|
'default_barcode_page_width_number' => 'Chiều rộng trang Mã vạch mặc định phải là dạng số.',
|
|
'default_barcode_page_width_required' => 'Chiều rộng trang Mã vạch mặc định là trường bắt buộc.',
|
|
'default_barcode_width_number' => 'Chiều rộng Mã vạch mặc định phải là dạng số.',
|
|
'default_barcode_width_required' => 'Chiều rộng Mã vạch mặc định là trường bắt buộc.',
|
|
'default_item_columns' => 'Mục hiển thị mặc định',
|
|
'default_origin_tax_code' => 'Mã thuế gốc mặc định',
|
|
'default_receivings_discount' => 'Giảm giá mặc định',
|
|
'default_receivings_discount_number' => 'Giảm giá mặc định phải là số.',
|
|
'default_receivings_discount_required' => 'Giảm giá mặc định là trường bắt buộc.',
|
|
'default_sales_discount' => 'Giảm giá bán hàng mặc định',
|
|
'default_sales_discount_number' => 'Giảm giá bán hàng mặc định phải là dạng số.',
|
|
'default_sales_discount_required' => 'Trường Giảm giá bán hàng mặc định là bắt buộc.',
|
|
'default_tax_category' => 'Danh mục thuế mặc định',
|
|
'default_tax_code' => 'Mã số thuế mặc định',
|
|
'default_tax_jurisdiction' => 'Thẩm quyền thuế mặc định',
|
|
'default_tax_name_number' => 'Tên Thuế mặc định phải ở dạng chuỗi văn bản.',
|
|
'default_tax_name_required' => 'Trường tên Thuế mặc định là bắt buộc.',
|
|
'default_tax_rate' => 'Tỷ lệ thuế mặc định %',
|
|
'default_tax_rate_1' => 'Tỷ lệ thuế 1',
|
|
'default_tax_rate_2' => 'Tỷ lệ thuế 2',
|
|
'default_tax_rate_3' => '',
|
|
'default_tax_rate_number' => 'Tỷ lệ thuế mặc định phải là dạng số.',
|
|
'default_tax_rate_required' => 'Trường Tỷ lệ thuế mặc định là bắt buộc.',
|
|
'derive_sale_quantity' => 'Cho phép suy luận số lượng bán hàng',
|
|
'derive_sale_quantity_tooltip' => 'Nếu chọn thì một kiểu hàng hóa mới sẽ được cung cấp cho đặt hàng hàng hóa theo tổng số mở rộng',
|
|
'dinner_table' => 'Bàn ăn',
|
|
'dinner_table_duplicate' => 'Bàn phải duy nhất.',
|
|
'dinner_table_enable' => 'Sử dụng bàn ăn',
|
|
'dinner_table_invalid_chars' => "Tên bàn ăn không được chứa '_'.",
|
|
'dinner_table_required' => 'Trường bàn ăn là bắt buộc.',
|
|
'dot' => 'dấu chấm',
|
|
'email' => 'Thư điện tử',
|
|
'email_configuration' => 'Cấu hình Thư điện tử',
|
|
'email_mailpath' => 'Đường dẫn đến Sendmail',
|
|
'email_protocol' => 'Giao thức',
|
|
'email_receipt_check_behaviour' => 'Dấu kiểm biên lai thư điện tử',
|
|
'email_receipt_check_behaviour_always' => 'Luôn đánh dấu kiểm',
|
|
'email_receipt_check_behaviour_last' => 'Nhớ lựa chọn cuối cùng',
|
|
'email_receipt_check_behaviour_never' => 'Luôn không đánh dấu kiểm',
|
|
'email_smtp_crypto' => 'Mã hóa SMTP',
|
|
'email_smtp_host' => 'Máy phục vụ SMTP',
|
|
'email_smtp_pass' => 'Mật khẩu SMTP',
|
|
'email_smtp_port' => 'Cổng SMTP',
|
|
'email_smtp_timeout' => 'Chờ tối đa SMTP',
|
|
'email_smtp_user' => 'Tài khoản SMTP',
|
|
'enable_avatar' => '',
|
|
'enable_avatar_tooltip' => '',
|
|
'enable_dropdown_tooltip' => '',
|
|
'enable_new_look' => '',
|
|
'enable_right_bar' => '',
|
|
'enable_right_bar_tooltip' => '',
|
|
'enforce_privacy' => 'Chính sách bắt buộc',
|
|
'enforce_privacy_tooltip' => 'Bảo vệ riêng tư khách hàng bắt buộc xáo trộn dữ liệu trong trường hợp dữ liệu của họ đang bị xóa',
|
|
'fax' => 'Fax',
|
|
'file_perm' => 'Có một số vấn đề với phân quyền tập tin vui lòng sửa và tải lại trang này.',
|
|
'financial_year' => 'Ngày đầu năm tài chính',
|
|
'financial_year_apr' => '1 tháng tư',
|
|
'financial_year_aug' => '1 tháng tám',
|
|
'financial_year_dec' => '1 tháng mười hai',
|
|
'financial_year_feb' => '1 tháng hai',
|
|
'financial_year_jan' => '1 tháng giêng',
|
|
'financial_year_jul' => '1 tháng bảy',
|
|
'financial_year_jun' => '1 tháng sáu',
|
|
'financial_year_mar' => '1 tháng ba',
|
|
'financial_year_may' => '1 tháng năm',
|
|
'financial_year_nov' => '1 tháng mười một',
|
|
'financial_year_oct' => '1 tháng mười',
|
|
'financial_year_sep' => '1 tháng chín',
|
|
'floating_labels' => '',
|
|
'gcaptcha_enable' => 'reCAPTCHA cho trang đăng nhập',
|
|
'gcaptcha_secret_key' => 'Khóa bí mật reCAPTCHA',
|
|
'gcaptcha_secret_key_required' => 'Khóa bí mật reCAPTCHA là trường bắt buộc',
|
|
'gcaptcha_site_key' => 'Khóa trang reCAPTCHA',
|
|
'gcaptcha_site_key_required' => 'Khóa trang reCAPTCHA là trường bắt buộc',
|
|
'gcaptcha_tooltip' => 'Bảo vệ trang đăng nhập bằng Google reCAPTCHA, bấm vào biểu tượng cho một cặp khóa API.',
|
|
'general' => 'Chung',
|
|
'general_configuration' => 'Thông tin chung',
|
|
'giftcard_number' => 'Số Thẻ quà tặng',
|
|
'giftcard_random' => 'Tạo ngẫu nhiên',
|
|
'giftcard_series' => 'Tạo dạng nối tiếp nhau',
|
|
'image_allowed_file_types' => 'Các kiểu tập tin được phép',
|
|
'image_max_height_tooltip' => 'Chiều cao ảnh tải lên tối đa được phép tính bằng điểm ảnh (px).',
|
|
'image_max_size_tooltip' => 'Kích cỡ tập tin ảnh tải lên tối đa được phép tính bằng kilobytes (kb).',
|
|
'image_max_width_tooltip' => 'Chiều rộng ảnh tải lên tối đa được phép tính bằng điểm ảnh (px).',
|
|
'image_restrictions' => 'Hạn chế tải ảnh lên',
|
|
'include_hsn' => 'Bao gồm hỗ trợ cho mã HSN',
|
|
'info' => 'Thông tin',
|
|
'info_configuration' => 'Thông tin cửa hàng',
|
|
'input_groups' => '',
|
|
'integrations' => 'Tích hợp',
|
|
'integrations_configuration' => 'Tích hợp bên thứ ba',
|
|
'invoice' => 'Hóa đơn',
|
|
'invoice_configuration' => 'Cài đặt in hóa đơn',
|
|
'invoice_default_comments' => 'Ghi chú mặc định cho hóa đơn',
|
|
'invoice_email_message' => 'Mẫu hóa đơn khi gửi thư',
|
|
'invoice_enable' => 'Bật Hóa đơn',
|
|
'invoice_printer' => 'Máy in hóa đơn',
|
|
'invoice_type' => 'Loại hóa đơn',
|
|
'is_readable' => 'là đọc được, nhưng quyền hạn được đặt chưa đúng. Vui lòng đặt nó thành 640 hoặc 660 sau đó tải lại.',
|
|
'is_writable' => 'là ghi được, nhưng quyền hạn được đặt chưa đúng. Vui lòng đặt nó thành 750 sau đó tải lại.',
|
|
'item_markup' => '',
|
|
'jsprintsetup_required' => 'Cảnh báo: Tính năng này chỉ làm việc nếu bạn đã cài đặt ứng dụng bổ xung jsPrintSetup cho FireFox. Cứ lưu chứ?',
|
|
'language' => 'Ngôn ngữ',
|
|
'last_used_invoice_number' => 'Số hóa đơn dùng lần cuối',
|
|
'last_used_quote_number' => 'Số báo giá dùng lần cuối',
|
|
'last_used_work_order_number' => 'Số W/O dùng lần cuối',
|
|
'left' => 'Trái',
|
|
'license' => 'Giấy phép',
|
|
'license_configuration' => 'Điều khoản của giấy phép',
|
|
'line_sequence' => 'Xếp dòng theo',
|
|
'lines_per_page' => 'Dòng trên mỗi trang',
|
|
'lines_per_page_number' => 'Dòng trên mỗi trang phải là dạng số.',
|
|
'lines_per_page_required' => 'Dòng trên mỗi trang là trường bắt buộc.',
|
|
'locale' => 'Định dạng',
|
|
'locale_configuration' => 'Cấu hình Bản địa hóa',
|
|
'locale_info' => 'Thông tin Cấu hình Bản địa hóa',
|
|
'location' => 'Kho',
|
|
'location_configuration' => 'Vị trí kho',
|
|
'location_info' => 'Thông tin cấu hình vị trí',
|
|
'login_form' => '',
|
|
'logout' => 'Bạn có muốn sao lưu dự phòng trước khi đăng xuất ra không? Bấm chuột vào [OK] để sao lưu hoặc [Cancel] để đăng xuất.',
|
|
'mailchimp' => 'Mailchimp',
|
|
'mailchimp_api_key' => 'Khóa API Mailchimp',
|
|
'mailchimp_configuration' => 'Cấu hình Mailchimp',
|
|
'mailchimp_key_successfully' => 'API Key hợp lệ.',
|
|
'mailchimp_key_unsuccessfully' => 'API Key không hợp lệ.',
|
|
'mailchimp_lists' => 'Bó thư Mailchimp',
|
|
'mailchimp_tooltip' => 'Bấm vào biểu tượng để lấy khóa API.',
|
|
'message' => 'Nhắn tin',
|
|
'message_configuration' => 'Cấu hình nhắn tin',
|
|
'msg_msg' => 'Tin nhắn văn bản đã lưu',
|
|
'msg_msg_placeholder' => 'Nếu bạn muốn dùng một mẫu thì lưu lại các tin nhắn SMS ở đây, nếu không thì để trống.',
|
|
'msg_pwd' => 'Mật khẩu SMS-API',
|
|
'msg_src' => 'Mã số bộ gửi SMS-API',
|
|
'msg_src_required' => 'Mã số bộ gửi SMS-API là trường bắt buộc',
|
|
'msg_uid' => 'Tài khoản SMS-API',
|
|
'msg_uid_required' => 'Tài khoản SMS-API là trường bắt buộc',
|
|
'multi_pack_enabled' => 'Nhiều gói cho mỗi mục',
|
|
'no_risk' => 'Không tiềm ẩn rủi ro / bảo mật nào.',
|
|
'none' => 'không',
|
|
'notify_alignment' => 'Vị trí của thông báo nổi lên',
|
|
'number_format' => 'Định dạng số',
|
|
'number_locale' => 'Định dạng số',
|
|
'number_locale_invalid' => 'Vị trí đã nhập không hợp lệ. Kiểm tra liên kết trong tooltip để tìm vị trí hợp lệ.',
|
|
'number_locale_required' => 'Vị trí số là trường bắt buộc.',
|
|
'number_locale_tooltip' => 'Tìm vị trí phù hợp thông qua liên kết này.',
|
|
'os_timezone' => 'Múi giờ OSPOS:',
|
|
'ospos_info' => 'Thông tin cài đặt OSPOS',
|
|
'payment_options_order' => 'Thứ tự tùy chọn thanh toán',
|
|
'payment_reference_code_length_limits' => 'Mã tham chiếu thanh toán<br>Giới hạn độ dài',
|
|
'payment_reference_code_length_max_label' => 'Tối đa',
|
|
'payment_reference_code_length_min_label' => 'Tối thiểu',
|
|
'perm_risk' => 'Phân quyền không đúng khiến phần mềm tiềm ẩn rủi ro.',
|
|
'phone' => 'Điện thoại công ty',
|
|
'phone_required' => 'Trường Điện thoại công ty là bắt buộc.',
|
|
'print_bottom_margin' => 'Lề dưới',
|
|
'print_bottom_margin_number' => 'Lề dưới phải là dạng số.',
|
|
'print_bottom_margin_required' => 'Trường Lề dưới là bắt buộc.',
|
|
'print_delay_autoreturn' => 'Tự động trả về trễ bán hàng',
|
|
'print_delay_autoreturn_number' => 'Tự động trả về trễ bán hàng là trường bắt buộc.',
|
|
'print_delay_autoreturn_required' => 'Tự động trả về trễ bán hàng phải là dạng số.',
|
|
'print_footer' => 'In phần chân trình duyệt',
|
|
'print_header' => 'In phần đầu trình duyệt',
|
|
'print_left_margin' => 'Lề trái',
|
|
'print_left_margin_number' => 'Lề trái phải ở dạng số.',
|
|
'print_left_margin_required' => 'Trường Lề trái là bắt buộc.',
|
|
'print_receipt_check_behaviour' => 'In dấu kiểm biên lai',
|
|
'print_receipt_check_behaviour_always' => 'Luôn đánh dấu kiểm',
|
|
'print_receipt_check_behaviour_last' => 'Nhớ lựa chọn cuối cùng',
|
|
'print_receipt_check_behaviour_never' => 'Luôn không đánh dấu kiểm',
|
|
'print_right_margin' => 'Lề phải',
|
|
'print_right_margin_number' => 'Lề trên phải ở dạng số.',
|
|
'print_right_margin_required' => 'Trường Lề trên là bắt buộc.',
|
|
'print_silently' => 'Hiển thị hộp thoại In',
|
|
'print_top_margin' => 'Lề trên',
|
|
'print_top_margin_number' => 'Lề trên phải ở dạng số.',
|
|
'print_top_margin_required' => 'Trường Lề trên là bắt buộc.',
|
|
'quantity_decimals' => 'Số lượng dấu thập phân',
|
|
'quick_cash_enable' => '',
|
|
'quote_default_comments' => 'Ghi chú báo giá mặc định',
|
|
'receipt' => 'Biên lai',
|
|
'receipt_category' => '',
|
|
'receipt_configuration' => 'Cài đặt in biên lai',
|
|
'receipt_default' => 'Mặc định',
|
|
'receipt_font_size' => 'Cỡ chữ',
|
|
'receipt_font_size_number' => 'Cỡ chữ phải là dạng số.',
|
|
'receipt_font_size_required' => 'Trường Cỡ chữ là bắt buộc.',
|
|
'receipt_info' => 'Thông tin cấu hình Biên lai',
|
|
'receipt_printer' => 'In vé',
|
|
'receipt_short' => 'Ngắn',
|
|
'receipt_show_company_name' => 'Hiển thị tên công ty',
|
|
'receipt_show_description' => 'Hiện mô tả',
|
|
'receipt_show_serialnumber' => 'Hiển thị số Sê-ri',
|
|
'receipt_show_tax_ind' => 'Hiển thị chỉ thị thuế',
|
|
'receipt_show_taxes' => 'Hiển thị Thuế',
|
|
'receipt_show_total_discount' => 'Hiển thị Giảm giá tổng cộng',
|
|
'receipt_template' => 'Mẫu biên lai',
|
|
'receiving_calculate_average_price' => 'Tính giá tb (Nhập hàng)',
|
|
'recv_invoice_format' => 'Định dạng hóa đơn nhận hàng',
|
|
'register_mode_default' => 'Chế độ Đăng ký mặc định',
|
|
'report_an_issue' => 'Báo cáo trục trặc',
|
|
'return_policy_required' => 'Trường Chính sách trả hàng là bắt buộc.',
|
|
'reward' => 'Điểm thưởng',
|
|
'reward_configuration' => 'Cấu hình Điểm thưởng',
|
|
'right' => 'Phải',
|
|
'sales_invoice_format' => 'Định dạng Hóa đơn bán hàng',
|
|
'sales_quote_format' => 'Định dạng Báo giá bán hàng',
|
|
'mailpath_invalid' => 'Đường dẫn sendmail không hợp lệ. Chỉ cho phép chữ cái, số, dấu gạch ngang, dấu gạch dưới, dấu gạch chéo, dấu gạch chéo ngược, dấu hai chấm, dấu cách và dấu chấm.',
|
|
'saved_successfully' => 'Cấu hình được lưu thành công.',
|
|
'saved_unsuccessfully' => 'Gặp lỗi khi lưu cấu hình.',
|
|
'security_issue' => 'Cảnh báo về lỗ hổng bảo mật',
|
|
'server_notice' => 'Vui lòng sử dụng các thông tin phía dưới để báo cáo lỗi.',
|
|
'service_charge' => '',
|
|
'show_due_enable' => '',
|
|
'show_office_group' => 'Hiển thị biểu tượng Văn phòng',
|
|
'statistics' => 'Gửi thống kê',
|
|
'statistics_tooltip' => 'Gửi thống kê cho nhà phát triển và mục đích cải tiến tính năng.',
|
|
'stock_location' => 'Vị trí kho',
|
|
'stock_location_duplicate' => 'Vị trí kho phải là duy nhất.',
|
|
'stock_location_invalid_chars' => "Vị trí kho không được chứa '_'.",
|
|
'stock_location_required' => 'Trường Vị trí kho là bắt buộc.',
|
|
'suggestions_fifth_column' => '',
|
|
'suggestions_first_column' => 'Cột 1',
|
|
'suggestions_fourth_column' => '',
|
|
'suggestions_layout' => 'Bố cục gợi ý tìm kiếm',
|
|
'suggestions_second_column' => 'Cột 2',
|
|
'suggestions_third_column' => 'Cột 3',
|
|
'system_conf' => 'Cài đặt & Cấu hình',
|
|
'system_info' => 'System Info',
|
|
'table' => 'Bàn ăn',
|
|
'table_configuration' => 'Cấu hình bàn ăn',
|
|
'takings_printer' => 'In Biên lai',
|
|
'tax' => 'Thuế',
|
|
'tax_category' => 'Thể loại thuế',
|
|
'tax_category_duplicate' => 'Thể loại thuế vừa nhập đã sẵn có.',
|
|
'tax_category_invalid_chars' => 'Thể loại thuế vừa nhập không hợp lệ.',
|
|
'tax_category_required' => 'Thể loại thuế là bắt buộc.',
|
|
'tax_category_used' => 'Không thể xóa Thể loại thuế bởi vì nó đang được dùng.',
|
|
'tax_configuration' => 'Cấu hình Thuế',
|
|
'tax_decimals' => 'Phần thập phân Thế',
|
|
'tax_id' => 'Mã số thuế',
|
|
'tax_included' => 'Gồm thuế',
|
|
'theme' => 'Giao diện',
|
|
'theme_preview' => '',
|
|
'thousands_separator' => 'Dấu ngăn cách phần nghìn',
|
|
'timezone' => 'Múi giờ',
|
|
'timezone_error' => 'Múi giờ OSPOS khác với múi giờ trên máy của bạn.',
|
|
'top' => 'Đỉnh',
|
|
'use_destination_based_tax' => 'Sử dụng thuế dựa trên điểm đến',
|
|
'user_timezone' => 'Múi giờ trên máy nội bộ:',
|
|
'website' => 'Website',
|
|
'wholesale_markup' => '',
|
|
'work_order_enable' => 'Hỗ trợ giao việc',
|
|
'work_order_format' => 'Định dạng giấy giao việc',
|
|
];
|