1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-13 22:54:25 +01:00

Merge pull request #10027 from turbo124/v5-develop

Add vi lang
This commit is contained in:
David Bomba 2024-09-22 06:37:24 +10:00 committed by GitHub
commit 096299a83c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 5941 additions and 30 deletions

View File

@ -75,7 +75,7 @@ class MobileLocalization extends Command
private function flutterResources()
{
$languages = cache('languages');
$languages = app('languages');
$resources = $this->getResources();
foreach ($languages as $language) {

View File

@ -76,6 +76,7 @@ class TranslationsExport extends Command
'sv',
'th',
'tr_TR',
'vi',
'zh_TW',
];

View File

@ -45,6 +45,8 @@ class QuickbooksSettings implements Castable
'purchase_order' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'product' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'payment' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'vendor' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
'expense' => ['sync' => true, 'update_record' => true, 'direction' => 'bidirectional'],
];

View File

@ -12,27 +12,34 @@
namespace App\Services\Quickbooks\Jobs;
use App\Models\Client;
use App\Models\Vendor;
use App\Models\Company;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Product;
use App\Libraries\MultiDB;
use Illuminate\Bus\Queueable;
use App\Factory\ClientFactory;
use App\Factory\VendorFactory;
use App\Factory\ExpenseFactory;
use App\Factory\InvoiceFactory;
use App\Factory\ProductFactory;
use App\Factory\ClientContactFactory;
use App\Factory\VendorContactFactory;
use App\DataMapper\QuickbooksSettings;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\Quickbooks\QuickbooksService;
use QuickBooksOnline\API\Data\IPPSalesReceipt;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use App\Services\Quickbooks\Transformers\ClientTransformer;
use App\Services\Quickbooks\Transformers\VendorTransformer;
use App\Services\Quickbooks\Transformers\ExpenseTransformer;
use App\Services\Quickbooks\Transformers\InvoiceTransformer;
use App\Services\Quickbooks\Transformers\PaymentTransformer;
use App\Services\Quickbooks\Transformers\ProductTransformer;
use QuickBooksOnline\API\Data\IPPSalesReceipt;
class QuickbooksSync implements ShouldQueue
{
@ -50,6 +57,7 @@ class QuickbooksSync implements ShouldQueue
'payment' => 'Payment',
'sales' => 'SalesReceipt',
'vendor' => 'Vendor',
'expense' => 'Purchase',
];
private QuickbooksService $qbs;
@ -109,8 +117,9 @@ class QuickbooksSync implements ShouldQueue
'product' => $this->syncQbToNinjaProducts($records),
'invoice' => $this->syncQbToNinjaInvoices($records),
'sales' => $this->syncQbToNinjaInvoices($records),
// 'vendor' => $this->syncQbToNinjaClients($records),
'vendor' => $this->syncQbToNinjaVendors($records),
// 'quote' => $this->syncInvoices($records),
'expense' => $this->syncQbToNinjaExpenses($records),
// 'purchase_order' => $this->syncInvoices($records),
// 'payment' => $this->syncPayment($records),
default => false,
@ -162,7 +171,7 @@ class QuickbooksSync implements ShouldQueue
$paymentable->paymentable_id = $invoice->id;
$paymentable->paymentable_type = 'invoices';
$paymentable->amount = $transformed['applied'] + $ninja_payment->credits->sum('amount');
$paymentable->created_at = $ninja_payment->date;
$paymentable->created_at = $ninja_payment->date; //@phpstan-ignore-line
$paymentable->save();
$invoice->service()->applyPayment($ninja_payment, $paymentable->amount);
@ -238,6 +247,60 @@ class QuickbooksSync implements ShouldQueue
}
}
private function syncQbToNinjaVendors(array $records): void
{
$transformer = new VendorTransformer($this->company);
foreach($records as $record)
{
$ninja_data = $transformer->qbToNinja($record);
if($vendor = $this->findVendor($ninja_data))
{
$vendor->fill($ninja_data[0]);
$vendor->saveQuietly();
$contact = $vendor->contacts()->where('email', $ninja_data[1]['email'])->first();
if(!$contact)
{
$contact = VendorContactFactory::create($this->company->id, $this->company->owner()->id);
$contact->vendor_id = $vendor->id;
$contact->send_email = true;
$contact->is_primary = true;
$contact->fill($ninja_data[1]);
$contact->saveQuietly();
}
elseif($this->updateGate('vendor')){
$contact->fill($ninja_data[1]);
$contact->saveQuietly();
}
}
}
}
private function syncQbToNinjaExpenses(array $records): void
{
$transformer = new ExpenseTransformer($this->company);
foreach($records as $record)
{
$ninja_data = $transformer->qbToNinja($record);
if($expense = $this->findExpense($ninja_data))
{
$expense->fill($ninja_data);
$expense->saveQuietly();
}
}
}
private function syncQbToNinjaProducts($records): void
{
$product_transformer = new ProductTransformer($this->company);
@ -254,6 +317,55 @@ class QuickbooksSync implements ShouldQueue
}
}
private function findExpense(array $qb_data): ?Expense
{
$expense = $qb_data;
$search = Expense::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('number', $expense['number']);
if($search->count() == 0) {
return ExpenseFactory::create($this->company->id, $this->company->owner()->id);
}
elseif($search->count() == 1) {
return $this->settings['expense']['update_record'] ? $search->first() : null;
}
return null;
}
private function findVendor(array $qb_data) :?Vendor
{
$vendor = $qb_data[0];
$contact = $qb_data[1];
$vendor_meta = $qb_data[2];
$search = Vendor::query()
->withTrashed()
->where('company_id', $this->company->id)
->where(function ($q) use ($vendor, $vendor_meta, $contact){
$q->where('vendor_hash', $vendor_meta['vendor_hash'])
->orWhere('number', $vendor['number'])
->orWhereHas('contacts', function ($q) use ($contact){
$q->where('email', $contact['email']);
});
});
if($search->count() == 0) {
//new client
return VendorFactory::create($this->company->id, $this->company->owner()->id);
}
elseif($search->count() == 1) {
return $this->settings['vendor']['update_record'] ? $search->first() : null;
}
return null;
}
private function findClient(array $qb_data) :?Client
{
$client = $qb_data[0];
@ -266,7 +378,7 @@ class QuickbooksSync implements ShouldQueue
->where(function ($q) use ($client, $client_meta, $contact){
$q->where('client_hash', $client_meta['client_hash'])
->orWhere('id_number', $client['id_number'])
->orWhere('number', $client['number'])
->orWhereHas('contacts', function ($q) use ($contact){
$q->where('email', $contact['email']);
});

View File

@ -21,7 +21,7 @@ class SdkWrapper
{
public const MAXRESULTS = 10000;
private $entities = ['Customer','Invoice','Item','SalesReceipt','Vendor'];
private $entities = ['Customer','Invoice','Item','SalesReceipt', 'Vendor', 'Purchase'];
private OAuth2AccessToken $token;
@ -180,14 +180,10 @@ class SdkWrapper
$total = $this->totalRecords($entity);
$total = min($max, $total);
nlog("total = {$total}");
// Step 3 & 4: Get chunks of records until the total required records are retrieved
do {
$limit = min(self::MAXRESULTS, $total - $start);
nlog("limit = {$limit}");
$recordsChunk = $this->queryData("select * from $entity", $start, $limit);
if(empty($recordsChunk)) {
break;
@ -195,7 +191,6 @@ class SdkWrapper
$records = array_merge($records, $recordsChunk);
$start += $limit;
nlog("start = {$start}");
} while ($start < $total);
if(empty($records)) {
@ -206,8 +201,6 @@ class SdkWrapper
nlog("Fetch Quickbooks API Error: {$th->getMessage()}");
}
// nlog($records);
return $records;
}
}

View File

@ -13,6 +13,7 @@
namespace App\Services\Quickbooks\Transformers;
use App\Models\Client;
use App\Models\Vendor;
use App\Models\Company;
/**
@ -31,10 +32,10 @@ class BaseTransformer
$country = app('countries')->first(function ($c) use ($iso_3_code){
/** @var \App\Models\Country $c */
return $c->iso_3166_3 == $iso_3_code;
return $c->iso_3166_3 == $iso_3_code || $c->name == $iso_3_code;
});
return $country ? (string) $country->id : '840';
return $country ? (string) $country->id : $this->company->settings->country_id;
}
public function resolveCurrency(string $currency_code): string
@ -47,7 +48,7 @@ class BaseTransformer
return $c->code == $currency_code;
});
return $currency ? (string) $currency->id : '1';
return $currency ? (string) $currency->id : $this->company->settings->currency_id;
}
public function getShipAddrCountry($data, $field)
@ -65,10 +66,20 @@ class BaseTransformer
$client = Client::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('id_number', $customer_reference_id)
->where('number', $customer_reference_id)
->first();
return $client ? $client->id : null;
}
public function getVendorId($customer_reference_id): ?int
{
$vendor = Vendor::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('number', $customer_reference_id)
->first();
return $vendor ? $vendor->id : null;
}
}

View File

@ -44,7 +44,7 @@ class ClientTransformer extends BaseTransformer
'address1' => data_get($data, 'BillAddr.Line1', ''),
'address2' => data_get($data, 'BillAddr.Line2', ''),
'city' => data_get($data, 'BillAddr.City', ''),
'country_id' => $this->resolveCountry(data_get($data, 'BillAddr.Country', '')),
'country_id' => $this->resolveCountry(data_get($data, 'BillAddr.CountryCode', '')),
'state' => data_get($data, 'BillAddr.CountrySubDivisionCode', ''),
'postal_code' => data_get($data, 'BillAddr.PostalCode', ''),
'shipping_address1' => data_get($data, 'ShipAddr.Line1', ''),
@ -53,7 +53,7 @@ class ClientTransformer extends BaseTransformer
'shipping_country_id' => $this->resolveCountry(data_get($data, 'ShipAddr.Country', '')),
'shipping_state' => data_get($data, 'ShipAddr.CountrySubDivisionCode', ''),
'shipping_postal_code' => data_get($data, 'BillAddr.PostalCode', ''),
'id_number' => data_get($data, 'Id.value', ''),
'number' => data_get($data, 'Id.value', ''),
];
$settings = ClientSettings::defaults();

View File

@ -0,0 +1,98 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Quickbooks\Transformers;
use App\Factory\ExpenseCategoryFactory;
use App\Models\ExpenseCategory;
use Illuminate\Support\Carbon;
/**
* Class ExpenseTransformer.
*/
class ExpenseTransformer extends BaseTransformer
{
public function qbToNinja(mixed $qb_data)
{
return $this->transform($qb_data);
}
public function ninjaToQb()
{
}
public function transform(mixed $data): array
{
$expense = [
'amount' => data_get($data, 'TotalAmt'),
'date' => Carbon::parse(data_get($data, 'TxnDate',''))->format('Y-m-d'),
'currency_id' => $this->resolveCurrency(data_get($data, 'CurrencyRef.value', '')),
'private_notes' => data_get($data, 'PrivateNote', null),
'public_notes' => null,
'number' => data_get($data, 'Id.value', null),
'transaction_reference' => data_get($data, 'DocNumber', ''),
'should_be_invoiced' => false,
'invoice_documents' => false,
'uses_inclusive_taxes' => false,
'calculate_tax_by_amount' => false,
'category_id' => $this->getCategoryId(data_get($data, 'AccountRef.name','')),
];
$mergeable = $this->resolveAttachedEntity($data);
$expense = array_merge($expense, $mergeable);
nlog($expense);
return $expense;
}
private function resolveAttachedEntity($entity)
{
$related = data_get($entity, 'EntityRef.type');
$entity_id = data_get($entity, 'EntityRef.value');
switch($related) {
case 'Vendor':
return ['vendor_id' => $this->getVendorId($entity_id)];
case 'Client':
return ['client_id' => $this->getClientId($entity_id)];
default:
return [];
}
}
private function getCategoryId($name): ?int
{
if(strlen($name) == 0)
return null;
$category = ExpenseCategory::where('company_id', $this->company->id)
->where('name', $name)
->first();
if(!$category){
$category = ExpenseCategoryFactory::create($this->company->id, $this->company->owner()->id);
$category->name = $name;
$category->save();
}
return $category->id;
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Invoice Ninja (https://clientninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Quickbooks\Transformers;
/**
* Class VendorTransformer.
*/
class VendorTransformer extends BaseTransformer
{
public function qbToNinja(mixed $qb_data)
{
return $this->transform($qb_data);
}
public function ninjaToQb()
{
}
public function transform(mixed $data): array
{
nlog($data);
$contact = [
'first_name' => data_get($data, 'GivenName'),
'last_name' => data_get($data, 'FamilyName'),
'phone' => data_get($data, 'PrimaryPhone.FreeFormNumber'),
'email' => data_get($data, 'PrimaryEmailAddr.Address'),
];
$vendor = [
'number' => data_get($data, 'Id.value'),
'name' => data_get($data, 'DisplayName'),
'address1' => data_get($data, 'BillAddr.Line1'),
'address2' => data_get($data, 'BillAddr.Line2'),
'city' => data_get($data, 'BillAddr.City'),
'state' => data_get($data, 'BillAddr.CountrySubDivisionCode'),
'postal_code' => data_get($data, 'BillAddr.PostalCode'),
'country_id' => $this->resolveCountry(data_get($data, 'BillAddr.CountryCode', data_get($data, 'BillAddr.Country',''))),
'website' => data_get($data, 'WebAddr.URI'),
'vat_number' => data_get($data, 'TaxIdentifier'),
'currency_id' => $this->resolveCurrency(data_get($data, 'CurrencyRef.value','')),
];
$new_vendor_merge = [
'vendor_hash' => data_get($data, 'V4IDPseudonym', \Illuminate\Support\Str::random(32)),
];
return [$vendor, $contact, $new_vendor_merge];
}
}

View File

@ -0,0 +1,40 @@
<?php
use App\Models\Currency;
use App\Models\Language;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Language::unguard();
$language = Language::find(42);
if (! $language) {
Language::create(['id' => 42, 'name' => 'Vietnamese', 'locale' => 'vi']);
}
if($currency = Currency::find(16)) {
$currency->symbol = '₫';
$currency->save();
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -38,7 +38,7 @@ class CurrenciesSeeder extends Seeder
['id' => 13, 'name' => 'Singapore Dollar', 'code' => 'SGD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 14, 'name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ',', 'swap_currency_symbol' => true],
['id' => 15, 'name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 16, 'name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => '', 'precision' => '0', 'thousand_separator' => '.', 'decimal_separator' => ','],
['id' => 16, 'name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => '', 'precision' => '0', 'thousand_separator' => '.', 'decimal_separator' => ','],
['id' => 17, 'name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => 'CHF', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'],
['id' => 18, 'name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 19, 'name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],

View File

@ -66,6 +66,7 @@ class LanguageSeeder extends Seeder
['id' => 39, 'name' => 'Hungarian', 'locale' => 'hu'],
['id' => 40, 'name' => 'French - Swiss', 'locale' => 'fr_CH'],
['id' => 41, 'name' => 'Lao', 'locale' => 'lo_LA'],
['id' => 42, 'name' => 'Vietnamese', 'locale' => 'vi'],
];
foreach ($languages as $language) {

View File

@ -5333,6 +5333,7 @@ $lang = array(
'country_Melilla' => 'Melilla',
'country_Ceuta' => 'Ceuta',
'country_Canary Islands' => 'Canary Islands',
'lang_Vietnamese' => 'Vietnamese',
'invoice_status_changed' => 'Please note that the status of your invoice has been updated. We recommend refreshing the page to view the most current version.',
'no_unread_notifications' => 'Youre all caught up! No new notifications.',
);

View File

@ -2489,6 +2489,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'local_storage_required' => 'Erreur: le stockage local n\'est pas accessible.',
'your_password_reset_link' => 'Réinitialiser votre mot de passe',
'subdomain_taken' => 'Ce sous-domaine est déjà utilisé',
'expense_mailbox_taken' => 'La boîte de réception entrante est déjà utilisée',
'expense_mailbox_invalid' => 'La boîte de réception entrante ne correspond pas au schéma requis',
'client_login' => 'Connexion client',
'converted_amount' => 'Montant converti',
'default' => 'Par défaut',
@ -3886,7 +3888,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'payment_method_saving_failed' => 'Ce mode de paiement ne peut pas être enregistré pour usage ultérieur.',
'pay_with' => 'Payer avec',
'n/a' => 'N/D',
'by_clicking_next_you_accept_terms' => 'En cliquant sur "Prochaine étape", vous acceptez les conditions.',
'by_clicking_next_you_accept_terms' => 'En cliquant sur "Suivant", vous acceptez les conditions.',
'not_specified' => 'Non spécifié',
'before_proceeding_with_payment_warning' => 'Avant de procéder au paiement, vous devez remplir les champs suivants',
'after_completing_go_back_to_previous_page' => 'Retournez à la page précédente après avoir complété',
@ -5326,6 +5328,11 @@ Développe automatiquement la section des notes dans le tableau de produits pour
'payment_failed' => 'Le paiement a échoué',
'ssl_host_override' => 'Substitution d\'hôte SSL',
'upload_logo_short' => 'Téléverser un logo',
'country_Melilla' => 'Melilla',
'country_Ceuta' => 'Ceuta',
'country_Canary Islands' => 'Îles Canaries',
'invoice_status_changed' => 'Veuillez noter que l\'état de votre facture a été mis à jour. Nous vous recommandons de rafraîchir la page pour afficher la version la plus récente.',
'no_unread_notifications' => 'Vous êtes à jour! Aucune nouvelle notification.',
);
return $lang;

View File

@ -1411,7 +1411,7 @@ Kom terug naar deze betaalmethode pagina zodra u de bedragen heeft ontvangen en
'payment_type_Swish' => 'Swish',
'payment_type_Alipay' => 'Alipay',
'payment_type_Sofort' => 'Sofort',
'payment_type_SEPA' => 'SEPA Automatisch incasso',
'payment_type_SEPA' => 'SEPA automatisch incasso',
'payment_type_Bitcoin' => 'Bitcoin',
'payment_type_GoCardless' => 'GoCardless',
'payment_type_Zelle' => 'Zelle',
@ -2413,7 +2413,7 @@ Kom terug naar deze betaalmethode pagina zodra u de bedragen heeft ontvangen en
'alipay' => 'Alipay',
'sofort' => 'Sofort',
'sepa' => 'SEPA Automatisch incasso',
'sepa' => 'SEPA automatisch incasso',
'name_without_special_characters' => 'Geef een naam op met enkel letters van a-z en spaties',
'enable_alipay' => 'Accepteer Alipay',
'enable_sofort' => 'Accepteer Europese banktransacties',
@ -2489,6 +2489,8 @@ Kom terug naar deze betaalmethode pagina zodra u de bedragen heeft ontvangen en
'local_storage_required' => 'Fout: lokale opslag is niet beschikbaar.',
'your_password_reset_link' => 'Uw Wachtwoord Resetlink',
'subdomain_taken' => 'Het subdomein is al in gebruik',
'expense_mailbox_taken' => 'Het postvak IN is al in gebruik',
'expense_mailbox_invalid' => 'Het postvak IN komt niet overeen met het vereiste schema',
'client_login' => 'Klantenlogin',
'converted_amount' => 'Omgezet bedrag',
'default' => 'Standaard',
@ -3886,7 +3888,7 @@ Kom terug naar deze betaalmethode pagina zodra u de bedragen heeft ontvangen en
'payment_method_saving_failed' => 'Betaalmethode kan niet opgeslagen worden voor toekomstig gebruik.',
'pay_with' => 'Betaal met',
'n/a' => 'Nvt',
'by_clicking_next_you_accept_terms' => 'Door op "Volgende stap" te klikken, accepteert u de voorwaarden.',
'by_clicking_next_you_accept_terms' => 'Door op "Volgende" te klikken gaat u akkoord met de voorwaarden.',
'not_specified' => 'Niet gespecificeerd',
'before_proceeding_with_payment_warning' => 'Voordat u doorgaat met betalen, moet u de volgende velden invullen',
'after_completing_go_back_to_previous_page' => 'Ga na voltooiing terug naar de vorige pagina.',
@ -5019,7 +5021,7 @@ E-mail: :email<b><br><b>',
'lang_French - Swiss' => 'Frans - Zwitsers',
'currency_swazi_lilangeni' => 'Swazische Lilangeni',
'income' => 'Inkomen',
'amount_received_help' => 'Vul hier een waarde in als het totaal ontvangen bedrag MEER was dan het factuurbedrag, of bij het vastleggen van een betaling zonder facturen. Anders moet dit veld leeg worden gelaten.',
'amount_received_help' => 'Vul hier een waarde in als het totaal ontvangen bedrag HOGER was dan het factuurbedrag, of bij het vastleggen van een betaling zonder facturen. Anders moet dit veld leeg worden gelaten.',
'vendor_phone' => 'Leverancier Telefoonnummer',
'mercado_pago' => 'Mercado Pago',
'mybank' => 'Mijn bank',
@ -5321,11 +5323,18 @@ E-mail: :email<b><br><b>',
'applies_to' => 'Is van toepassing op',
'accept_purchase_order' => 'Accepteer inkooporder',
'round_to_seconds' => 'Afronden op seconden',
'activity_142' => 'Quote :number reminder 1 sent',
'activity_143' => 'Auto Bill succeeded for invoice :invoice',
'activity_144' => 'Auto Bill failed for invoice :invoice. :notes',
'activity_145' => 'EInvoice :invoice for :client was e-delivered. :notes',
'activity_142' => '1 herinnering verzonden voor offerte :number',
'activity_143' => 'Factuur :invoice is succesvol geïncasseerd',
'activity_144' => 'Incasso mislukt voor factuur :invoice. :notes',
'activity_145' => 'E-factuur :invoice voor :client is afgeleverd. :notes',
'payment_failed' => 'Betaling mislukt',
'ssl_host_override' => 'SSL Host Override',
'upload_logo_short' => 'Logo uploaden',
'country_Melilla' => 'Melilla',
'country_Ceuta' => 'Ceuta',
'country_Canary Islands' => 'Canarische Eilanden',
'invoice_status_changed' => 'Please note that the status of your invoice has been updated. We recommend refreshing the page to view the most current version.',
'no_unread_notifications' => 'Youre all caught up! No new notifications.',
);
return $lang;

20
lang/vi/auth.php Normal file
View File

@ -0,0 +1,20 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => 'These credentials do not match our records.',
'password' => 'The provided password is incorrect.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];

19
lang/vi/pagination.php Normal file
View File

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '« Previous',
'next' => 'Next »',
];

23
lang/vi/passwords.php Normal file
View File

@ -0,0 +1,23 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
'password' => 'Passwords must be at least six characters and match the confirmation.',
'reset' => 'Your password has been reset!',
'sent' => 'We have e-mailed your password reset link!',
'token' => 'This password reset token is invalid.',
'user' => "We can't find a user with that e-mail address.",
'throttled' => 'You have requested password reset recently, please check your email.',
];

5340
lang/vi/texts.php Normal file

File diff suppressed because it is too large Load Diff

170
lang/vi/validation.php Normal file
View File

@ -0,0 +1,170 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => 'The :attribute must be accepted.',
'accepted_if' => 'The :attribute must be accepted when :other is :value.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
'alpha' => 'The :attribute must only contain letters.',
'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.',
'alpha_num' => 'The :attribute must only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => 'The :attribute must be a date before :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
'between' => [
'array' => 'The :attribute must have between :min and :max items.',
'file' => 'The :attribute must be between :min and :max kilobytes.',
'numeric' => 'The :attribute must be between :min and :max.',
'string' => 'The :attribute must be between :min and :max characters.',
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'current_password' => 'The password is incorrect.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
'declined' => 'The :attribute must be declined.',
'declined_if' => 'The :attribute must be declined when :other is :value.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'ends_with' => 'The :attribute must end with one of the following: :values.',
'enum' => 'The selected :attribute is invalid.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'gt' => [
'array' => 'The :attribute must have more than :value items.',
'file' => 'The :attribute must be greater than :value kilobytes.',
'numeric' => 'The :attribute must be greater than :value.',
'string' => 'The :attribute must be greater than :value characters.',
],
'gte' => [
'array' => 'The :attribute must have :value items or more.',
'file' => 'The :attribute must be greater than or equal to :value kilobytes.',
'numeric' => 'The :attribute must be greater than or equal to :value.',
'string' => 'The :attribute must be greater than or equal to :value characters.',
],
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'lt' => [
'array' => 'The :attribute must have less than :value items.',
'file' => 'The :attribute must be less than :value kilobytes.',
'numeric' => 'The :attribute must be less than :value.',
'string' => 'The :attribute must be less than :value characters.',
],
'lte' => [
'array' => 'The :attribute must not have more than :value items.',
'file' => 'The :attribute must be less than or equal to :value kilobytes.',
'numeric' => 'The :attribute must be less than or equal to :value.',
'string' => 'The :attribute must be less than or equal to :value characters.',
],
'mac_address' => 'The :attribute must be a valid MAC address.',
'max' => [
'array' => 'The :attribute must not have more than :max items.',
'file' => 'The :attribute must not be greater than :max kilobytes.',
'numeric' => 'The :attribute must not be greater than :max.',
'string' => 'The :attribute must not be greater than :max characters.',
],
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
'array' => 'The :attribute must have at least :min items.',
'file' => 'The :attribute must be at least :min kilobytes.',
'numeric' => 'The :attribute must be at least :min.',
'string' => 'The :attribute must be at least :min characters.',
],
'multiple_of' => 'The :attribute must be a multiple of :value.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',
'password' => [
'letters' => 'The :attribute must contain at least one letter.',
'mixed' => 'The :attribute must contain at least one uppercase and one lowercase letter.',
'numbers' => 'The :attribute must contain at least one number.',
'symbols' => 'The :attribute must contain at least one symbol.',
'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
],
'present' => 'The :attribute field must be present.',
'prohibited' => 'The :attribute field is prohibited.',
'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
'prohibits' => 'The :attribute field prohibits :other from being present.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_array_keys' => 'The :attribute field must contain entries for: :values.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values are present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'size' => [
'array' => 'The :attribute must contain :size items.',
'file' => 'The :attribute must be :size kilobytes.',
'numeric' => 'The :attribute must be :size.',
'string' => 'The :attribute must be :size characters.',
],
'starts_with' => 'The :attribute must start with one of the following: :values.',
'doesnt_start_with' => 'The :attribute may not start with one of the following: :values.',
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid timezone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute must be a valid URL.',
'uuid' => 'The :attribute must be a valid UUID.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute placeholder
| with something more reader friendly such as "E-Mail Address" instead
| of "email". This simply helps us make our message more expressive.
|
*/
'attributes' => [],
];