Giter Club home page Giter Club logo

matice's People

Contributors

genl avatar gildastema avatar mgralikowski avatar patrickomeara avatar rocketc31 avatar tortuetorche avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

matice's Issues

window.navigator.language and navigator.language does not return same language as getLocale()

Expected behavior

I've installed this in laravel project with jetstream.

  1. Add @translations to app.blade.php in header
  2. Ran composer require genl/matice and npm install matice
  3. Added the import and methods in app.js
  4. Added translations for both da and da-DK and would expect this work when my browser is da-DK

So basics issue is:
Matice getLocale() should return same language as window.navigator.language or navigator.language

Current behavior

For debugging I put these in app.js
console.log(`Vue locale ${getLocale()}, browser locale ${navigator.language}`)
console.log(locales())
with this result:
Vue locale en, browser locale da-DK
["da", "da-DK", "en"]

The english translation works and it is being updated whenever I change something, but I can't get it to display the danish translation. It might very well be a mistake I made myself, but I can't pinpoint where it is.

Versions

  • Laravel: 8.22.1
  • Matice: 1.1.4

Translations

resources/lang/da-DK/dashboard.php and same content for resources/lang/da/dashboard.php

return [
    'profile' => 'Med din FCK Profil har du alt FCK-relateret indhold samlet ét sted. Tilpas din profil, så du får lige nøjagtigt det ud af din fodboldklub, som du gerne vil have.',
];

{{ $trans('dashboard.profile') }}

Contents of Matice.translations

image

Unhandled Promise Rejection: Locale [nl_BE] does not exist in Safari

Hi, thanks for the package. I'm using it with Laravel and Jetstream / Inertia.
In Chrome everything works fine but when opening my project in Safari it wouldn't load because of the following error:

Unhandled Promise Rejection: Locale [nl_US] does not exist.

Versions

  • Laravel: 8.47.0
  • Matice: 1.1.6

Yarn audit -> security issues

❌ Audit report

Versions

  • Laravel: 10.x
  • Matice: 1.1.4

Description

According to npmjs.com advisories there're several critical issue within the dependencies, some with available patch
Running the command yarn audit i got the following result:

yarn audit v1.22.19
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ high          │ Authorization Bypass in parse-path                           │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ parse-path                                                   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=5.0.0                                                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > git-url-parse > git-up > parse-url >   │
│               │ parse-path                                                   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1088916                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ moderate      │ Got allows a redirect to a UNIX socket                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ got                                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=11.8.5                                                     │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > got                                    │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1088948                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ moderate      │ Got allows a redirect to a UNIX socket                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ got                                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=11.8.5                                                     │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > update-notifier > latest-version >     │
│               │ package-json > got                                           │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1088948                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ moderate      │ parse-url parses http URLs incorrectly, making it vulnerable │
│               │ to host name spoofing                                        │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ parse-url                                                    │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=8.1.0                                                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > git-url-parse > git-up > parse-url     │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1089114                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ critical      │ Server-Side Request Forgery (SSRF) in GitHub repository      │
│               │ ionicabizau/parse-url                                        │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ parse-url                                                    │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=8.1.0                                                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > git-url-parse > git-up > parse-url     │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1092304                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ moderate      │ semver vulnerable to Regular Expression Denial of Service    │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ semver                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=7.5.2                                                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > semver                                 │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1092461                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ critical      │ vm2 Sandbox Escape vulnerability                             │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ vm2                                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ No patch available                                           │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > proxy-agent > pac-proxy-agent >        │
│               │ pac-resolver > degenerator > vm2                             │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1092502                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ critical      │ vm2 Sandbox Escape vulnerability                             │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ vm2                                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ No patch available                                           │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ matice                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ matice > release-it > proxy-agent > pac-proxy-agent >        │
│               │ pac-resolver > degenerator > vm2                             │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1092538                     │
└───────────────┴──────────────────────────────────────────────────────────────┘

Is there a way to remove comments?

Hi,

Is there a way to remove the following comment being generated with this package?

<!-- Matice Laravel Translations generated -->
--
<!-- Used cached translations at: /home/forge/xxxx/xxx/xxxx/resources/assets/js/translations.js -->

[question] New package version publish?

Hey, thanks for all your work on this package!

With the PR to fix the release-it vulerabilities merged in, could we get a new package version published? I'd love to finally silence the screaming vulnerability alerts I'm getting :)

this.$forceUpdate does not refresh the whole page when choosing another language

Expected behavior

All Vue components on the page are updated when the language is changed.

Current behavior

Only the language selector is updated and the rest of the page remains the same. this.$forceUpdate() seems not to work.

Versions

  • Laravel: 8.43.0
  • Matice: 1.1.4
  • Vue: 3.0.5

Description

The backend package works fine and the translations on the frontend, when I change the language on the Laravel config side, works wonderfully too. The problem here is that when I change the language with a selector I have created on the frontend, the this.$forceUpdate() method won't refresh all components on the page, so the translation only applies to the selector itself and nowhere else on the page.

I am using Vue3 if that matters. Here is my code on app.js:

...

createApp({
    render: () =>
        h(InertiaApp, {
            initialPage: JSON.parse(el.dataset.page),
            resolveComponent: (name) => require(`./Pages/${name}`).default,
        }),
})
    .mixin({
        methods: {
            route,
            $trans: trans,
            $__: __,
            $transChoice: transChoice,
            $setLocale(locale) {
                if (this.$locale() !== locale) {
                    setLocale(locale);
                    this.$forceUpdate(); // Refresh the vue instance(The whole app in case of SPA) after the locale changes.
                }
            },
            // The current locale
            $locale() {
                return getLocale()
            },
            // A listing of the available locales
            $locales() {
                return locales()
            }
        }
    })
    .use(InertiaPlugin)
    .mount(el);
    
    ...

and this is the code that calls the $setLocale(locale) method on my language selector component:

...

<select v-on:change="$setLocale($event.target.value)" v-model="selectedLang">
    <option v-for="(lang, code) in languages" :value="code">
        {{ lang }}
    </option>
</select>

...

No named export in generated translations.js file

Expected behavior

In readme.md has been added example of matice_translation.js generated by command php artisan matice:generate.

// matice_translations.js

const Matice = {
    locale: 'en',
    fallbackLocale: 'en',
    translations: {
      en: {
        auth: {
          failed: 'These credentials do not match our records.',
          throttle: 'Too many login attempts. Please try again in :seconds seconds.'
        }
      }
    }
};

export { Matice }; // expected named export

There is not named export in file matice_translations.js after generating.

Current behavior

Now generated JS file looks like below

// matice_translations.js

const Matice = {
    locale: 'en',
    fallbackLocale: 'en',
    translations: {
      en: {
        auth: {
          failed: 'These credentials do not match our records.',
          throttle: 'Too many login attempts. Please try again in :seconds seconds.'
        }
      }
    }
};

Versions

  • Laravel: 10.26.2
  • Matice: 1.1.4

Possible solution


        $generatedTranslations = "
/*
|--------------------------------------------------------------------------
| Generated Laravel translations
|--------------------------------------------------------------------------
|
| This file is autogenerated and should not be modified.
| To load new translations run: php artisan matice:generate
|
*/\n\n" . $generatedTranslations . "\nexport { Matice };\n";

in file matice/src/Commands/TranslationsGeneratorCommand.php append . "\nexport { Matice };\n";

Fallback locale not working

Situation: the locale is "de", the fallback locale is "en" and there are only translations for "en"

Expected behavior

calling the trans( function should output an 'en' translation

Current behavior

matice throws a Locale [de] does not exist. error.
This happens because the findSentence immediately calls the translations function which does not check the fallbackLocale variable and thus fails.

Versions

  • Laravel: 8.75.0
  • Matice: 1.1.6

Pluralization doesn't match Laravel's conventions

Hey @GENL this is a great library, well done.

One things I find strange is the difference the library has with Laravel translations.

Laravel would handle a translation with two parts 1 day|:count days as singular and plural. However it seems that matice handles it as zero and singular.

else if (parts.length === 2) parts = [parts[0], parts[1], parts[1]]

If I add {1} in front then it works fine {1} 1 day|:count days, however this is a workaround, they should follow the Laravel convention as they are shared between both.

I use the translation string as the key.

Expected behavior

1 day|:count days to be singular and plural

Current behavior

1 day|:count days is zero and singular

Versions

  • Laravel: 9.10.0
  • Matice: 1.1.7

Documentation Question: What do you have to look out for when using "Inertia"?

Currently, I'm developing my first Inertia project. Everything that should work works correctly. But I'm still not sure if I'm missing something.

For example do I need: php artisan matice:generate for Inertia?

Currently, I'm just using @translations in my app.blade.php. When I'm switching the languages, I'm using the normal <a> tag, not the Inertia links.

But I noticed that @translations is creating all languages:

de: {auth: {…}, pagination: {…}, passwords: {…}, routes: {…}, validation: {…}}
en: {auth: {…}, pagination: {…}, passwords: {…}, routes: {…}, validation: {…}}
hu: {auth: {…}, pagination: {…}, passwords: {…}, routes: {…}, validation: {…}} 

In #10 you are saying: "In production, matice lazy loads the translations.".

I just tried to set APP_ENV=production but it's still the same. Maybe you mean the generated js file from php artisan matice:generate?

Thank you! And p.s. this is a really great package! Really well done!

Pluralization with two parts (singular and plural) breaks when a comma is part of the translated text

Expected behavior

When relying on the "two parts" pluralization logic, the pluralized part of the translated string should be the one returned whenever the count parameter is greater than 1. At least this is how I understand how this logic should work.

Current behavior

Whenever there's a comma on the translated expression, the logic breaks and the first part (singular) is the one always returned, regardless the value of the count parameter.

Versions

  • Laravel: 9.24.0
  • Matice: 1.1.4

Transalations

// lang/pt_BR/welcome.php
[
    'greet' => [
        // ...
        'people' => "Olá, Marcos!|Olá para todos!",
    ],
]

__('welcome.greet.people', { args: { count: 2 }, pluralize: true }) // Olá, Marcos

Contents of Matice.translations

{
"en_US": {
"auth": {
"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."
},
"localization": {
"locale": {
"en_US": "English",
"pt_BR": "Portuguese"
}
},
"pagination": {
"previous": "« Previous",
"next": "Next »"
},
"passwords": {
"reset": "Your password has been reset!",
"sent": "We have emailed your password reset link!",
"throttled": "Please wait before retrying.",
"token": "This password reset token is invalid.",
"user": "We can't find a user with that email address."
},
"validation": {
"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": {
"numeric": "The :attribute must be between :min and :max.",
"file": "The :attribute must be between :min and :max kilobytes.",
"string": "The :attribute must be between :min and :max characters.",
"array": "The :attribute must have between :min and :max items."
},
"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": {
"numeric": "The :attribute must be greater than :value.",
"file": "The :attribute must be greater than :value kilobytes.",
"string": "The :attribute must be greater than :value characters.",
"array": "The :attribute must have more than :value items."
},
"gte": {
"numeric": "The :attribute must be greater than or equal to :value.",
"file": "The :attribute must be greater than or equal to :value kilobytes.",
"string": "The :attribute must be greater than or equal to :value characters.",
"array": "The :attribute must have :value items or more."
},
"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.",
"mac_address": "The :attribute must be a valid MAC address.",
"json": "The :attribute must be a valid JSON string.",
"lt": {
"numeric": "The :attribute must be less than :value.",
"file": "The :attribute must be less than :value kilobytes.",
"string": "The :attribute must be less than :value characters.",
"array": "The :attribute must have less than :value items."
},
"lte": {
"numeric": "The :attribute must be less than or equal to :value.",
"file": "The :attribute must be less than or equal to :value kilobytes.",
"string": "The :attribute must be less than or equal to :value characters.",
"array": "The :attribute must not have more than :value items."
},
"max": {
"numeric": "The :attribute must not be greater than :max.",
"file": "The :attribute must not be greater than :max kilobytes.",
"string": "The :attribute must not be greater than :max characters.",
"array": "The :attribute must not have more than :max items."
},
"mimes": "The :attribute must be a file of type: :values.",
"mimetypes": "The :attribute must be a file of type: :values.",
"min": {
"numeric": "The :attribute must be at least :min.",
"file": "The :attribute must be at least :min kilobytes.",
"string": "The :attribute must be at least :min characters.",
"array": "The :attribute must have at least :min items."
},
"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": "The password is incorrect.",
"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_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": {
"numeric": "The :attribute must be :size.",
"file": "The :attribute must be :size kilobytes.",
"string": "The :attribute must be :size characters.",
"array": "The :attribute must contain :size items."
},
"starts_with": "The :attribute must 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": {
"attribute-name": {
"rule-name": "custom-message"
}
},
"attributes": []
},
"welcome": {
"greet": {
"me": "Hello!",
"someone": "Hello, :name!",
"me_more": "Hello, Ekcel Henrich!",
"people": "Hello, Ekcel!|Hello, everyone!"
},
"last_login": "{0} First login|[1,] Last login was :count minutes ago",
"balance": "{0} You're broke|[1000, 5000] a middle man|[1000000,
] You are awesome :name, :count Million Dollars"
},
"Welcome!": "Welcome, my friend!",
"Welcome, :name!": "Welcome, :name! Make yourself at home."
},
"pt_BR": {
"auth": {
"failed": "Essas credenciais não correspondem aos nossos registros.",
"password": "A senha fornecida está incorreta.",
"throttle": "Muitas tentativas de login. Tente novamente em :seconds segundos."
},
"localization": {
"locale": {
"en_US": "Inglês",
"pt_BR": "Português"
}
},
"pagination": {
"previous": "« Anterior",
"next": "Próxima »"
},
"passwords": {
"reset": "Sua senha foi alterada!",
"sent": "Enviamos seu link de redefinição de senha por e-mail!",
"throttled": "Aguarde antes de tentar novamente.",
"token": "Este token de redefinição de senha é inválido.",
"user": "Não encontramos um usuário com esse endereço de e-mail."
},
"validation": {
"accepted": "O :attribute deve ser aceito.",
"accepted_if": "O :attribute deve ser aceito quando :other for :value.",
"active_url": "O :attribute não é um URL válido.",
"after": "O :attribute deve ser uma data posterior a :date.",
"after_or_equal": "O :attribute deve ser uma data posterior ou igual a :date.",
"alpha": "O :attribute deve conter apenas letras.",
"alpha_dash": "O :attribute deve conter apenas letras, números, traços e sublinhados.",
"alpha_num": "O :attribute deve conter apenas letras e números.",
"array": "O :attribute deve ser um array.",
"before": "O :attribute deve ser uma data anterior a :date.",
"before_or_equal": "O :attribute deve ser uma data anterior ou igual a :date.",
"between": {
"numeric": "O :attribute deve estar entre :min e :max.",
"file": "O :attribute deve estar entre :min e :max kilobytes.",
"string": "O :attribute deve estar entre :min e :max caracteres.",
"array": "O :attribute deve ter entre :min e :max itens."
},
"boolean": "O campo :attribute deve ser verdadeiro ou falso.",
"confirmed": "A confirmação :attribute não corresponde.",
"current_password": "A senha está incorreta.",
"date": "O :attribute não é uma data válida.",
"date_equals": "O :attribute deve ser uma data igual a :date.",
"date_format": "O :attribute não corresponde ao formato :format.",
"declined": "O :attribute deve ser recusado.",
"declined_if": "O :attribute deve ser recusado quando :other for :value.",
"different": "O :attribute e :other devem ser diferentes.",
"digits": "O :attribute deve ser :digits dígitos.",
"digits_between": "O :attribute deve estar entre :min e :max dígitos.",
"dimensions": "O :attribute tem dimensões de imagem inválidas.",
"distinct": "O campo :attribute tem um valor duplicado.",
"email": "O :attribute deve ser um endereço de e-mail válido.",
"ends_with": "O :attribute deve terminar com um dos seguintes: :values.",
"enum": "O :attribute selecionado é inválido.",
"exists": "O :attribute selecionado é inválido.",
"file": "O :attribute deve ser um arquivo.",
"filled": "O campo :attribute deve ter um valor.",
"gt": {
"numeric": "O :attribute deve ser maior que :value.",
"file": "O :attribute deve ser maior que :value kilobytes.",
"string": "O :attribute deve ser maior que os caracteres :value.",
"array": "O :attribute deve ter mais de :value itens."
},
"gte": {
"numeric": "O :attribute deve ser maior ou igual a :value.",
"file": "O :attribute deve ser maior ou igual a :value kilobytes.",
"string": "O :attribute deve ser maior ou igual a :value caracteres.",
"array": "O :attribute deve ter itens :value ou mais."
},
"image": "O :attribute deve ser uma imagem.",
"in": "O :attribute selecionado é inválido.",
"in_array": "O campo :attribute não existe em :other.",
"integer": "O :attribute deve ser um número inteiro.",
"ip": "O :attribute deve ser um endereço IP válido.",
"ipv4": "O :attribute deve ser um endereço IPv4 válido.",
"ipv6": "O :attribute deve ser um endereço IPv6 válido.",
"mac_address": "O :attribute deve ser um endereço MAC válido.",
"json": "O :attribute deve ser uma string JSON válida.",
"lt": {
"numeric": "O :attribute deve ser menor que :value.",
"file": "O :attribute deve ser menor que :value kilobytes.",
"string": "O :attribute deve ser menor que :value caracteres.",
"array": "O :attribute deve ter itens menores que :value."
},
"lte": {
"numeric": "O :attribute deve ser menor ou igual a :value.",
"file": "O :attribute deve ser menor ou igual a :value kilobytes.",
"string": "O :attribute deve ser menor ou igual a :value caracteres.",
"array": "O :attribute não deve ter mais do que :value itens."
},
"max": {
"numeric": "O :attribute não deve ser maior que :max.",
"file": "O :attribute não deve ser maior que :max kilobytes.",
"string": "O :attribute não deve ser maior que :max caracteres.",
"array": "O :attribute não deve ter mais do que :max itens."
},
"mimes": "O :attribute deve ser um arquivo do tipo: :values.",
"mimetypes": "O :attribute deve ser um arquivo do tipo: :values.",
"min": {
"numeric": "O :attribute deve ser pelo menos :min.",
"file": "O :attribute deve ter pelo menos :min kilobytes.",
"string": "O :attribute deve ter pelo menos :min caracteres.",
"array": "O :attribute deve ter pelo menos :min itens."
},
"multiple_of": "O :attribute deve ser um múltiplo de :value.",
"not_in": "O :attribute selecionado é inválido.",
"not_regex": "O formato :attribute é inválido.",
"numeric": "O :attribute deve ser um número.",
"password": "A senha está incorreta.",
"present": "O campo :attribute deve estar presente.",
"prohibited": "O campo :attribute é proibido.",
"prohibited_if": "O campo :attribute é proibido quando :other é :value.",
"prohibited_unless": "O campo :attribute é proibido a menos que :other esteja em :values.",
"prohibits": "O campo :attribute proíbe :other de estar presente.",
"regex": "O formato :attribute é inválido.",
"required": "O campo :attribute é obrigatório.",
"required_if": "O campo :attribute é obrigatório quando :other é :value.",
"required_unless": "O campo :attribute é obrigatório, a menos que :other esteja em :values.",
"required_with": "O campo :attribute é obrigatório quando :values ​​está presente.",
"required_with_all": "O campo :attribute é obrigatório quando :values ​​estão presentes.",
"required_without": "O campo :attribute é obrigatório quando :values ​​não está presente.",
"required_without_all": "O campo :attribute é obrigatório quando nenhum dos :values ​​está presente.",
"same": "O :attribute e :other devem corresponder.",
"size": {
"numeric": "O :attribute deve ser :size.",
"file": "O :attribute deve ser :size kilobytes.",
"string": "O :attribute deve ser :size caracteres.",
"array": "O :attribute deve conter :size itens."
},
"starts_with": "O :attribute deve começar com um dos seguintes: :values.",
"string": "O :attribute deve ser uma string.",
"timezone": "O :attribute deve ser um fuso horário válido.",
"unique": "O :attribute já foi usado.",
"uploaded": "O :attribute falhou ao carregar.",
"url": "O :attribute deve ser um URL válido.",
"uuid": "O :attribute deve ser um UUID válido.",
"custom": {
"attribute-name": {
"rule-name": "custom-message"
}
},
"attributes": []
},
"welcome": {
"greet": {
"me": "Olá!",
"someone": "Olá, :name!",
"me_more": "Olá, Marcos Penna!",
"people": "Olá, Marcos!|Olá para todos!"
},
"last_login": "{0} Primeiro login|[1,] Último login há :count minutos",
"balance": "{0} Você está quebrado|[1000, 5000] classe média|[1000000,
] Você é incível, :name, :count milhões de dólares"
},
"Welcome!": "Bem-vindo!",
"Welcome, :name!": "Bem-vindo, :name!",
"Appointments": "Agendamentos",
"Date": "Data",
"Scheduled": "Horário",
"Registered": "Registrado",
"Patient Arrival": "Chegada Paciente",
"Examination Start": "Início Exame",
"Patient": "Paciente",
"Examination": "Exame",
"Actions": "Ações",
"Rows per page": "Registros por página",
"Search": "Buscar",
"arrival": "Chegada",
"exam": "Exame"
}
}

Updating Language using Inertia.js cause error

Expected behavior

When changing language should change language

Current behavior

Error when changing language and not working

Snippet calling setLocale
<b-dropdown-item v-for="locale in $locales()" :key="locale" @click="$setLocale(locale)">{{ upperCase(locale) }}</b-dropdown-item>

app.js

//...

import {__, setLocale, getLocale, transChoice, MaticeLocalizationConfig, locales} from "matice"

Vue.mixin({
    methods: {
        $__: __,
        $transChoice: transChoice,
        $setLocale: (locale) => {
          setLocale(locale);
          this.$forceUpdate() // Refresh the vue instance after locale change.
        },
        // The current locale
        $locale() {
            return getLocale()
        },
        // A listing of the available locales
        $locales() {
            return locales()
        }
    },
})

const app = document.getElementById('app');

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

new Vue({
    render: (h) =>
        h(InertiaApp, {
            props: {
                initialPage: JSON.parse(app.dataset.page),
                resolveComponent: (name) => require(`./Pages/${name}`).default,
            },
        }),
}).$mount(app); 

Error

Versions

  • Laravel: 8.19.0
  • Matice: 1.1.3
  • Inertia.js:0.2.15

Translations not being generated

Expected behavior

We are using three languages (EN, DE AND NL). In the DE version there are special characters such as ä. For some reason it seems to work in some of the translation files, while in others it causes the @translations string in the app blade to become empty and causes the whole application to no longer render.

Versions

  • Laravel: 8.83.23
  • Matice: 1.1

Transalations

Archief.zip

Contents of Matice.translations

???

Always add Fallback language

Expected behavior

When using @translations, I want to get the translations of the current user language and the fallback language, but not of all languages.

Current behavior

@translations provides all languages which can be very large. @translations("de") only provides German but not the specified fallback language which can result in errors.

Versions

  • Laravel: 9.54.2
  • Matice: 1.1.7

Contents of Matice.translations

With @translations:
image

With @translations("de"):

image


Is there an easy way to fix this? Pushing all language data over the wire seems wasteful. Thank you for any help.

Translation key with dot should works

Expected behavior

Javascript running in the web console:

trans('Whoops! Something went wrong.'); // -> "Oups ! Un problème est survenu."

trans('Whoops!'); // -> "Oups !"

Current behavior

Javascript running in the web console:

// Error
trans('Whoops! Something went wrong.'); // -> Error:  Uncaught Translation key not found : "Whoops! Something went wrong." -> Exactly "Whoops! Something went wrong" not found

// OK
trans('Whoops!'); // -> "Oups !"

Versions

  • Laravel: 6.20.2
  • Matice: 1.1.1

Translations

resources/lang/fr.json:

{
    "Whoops! Something went wrong." : "Oups ! Un problème est survenu.",
    "Whoops!" : "Oups !"
}

Comments

I think the root of this issue is here:

const parts = key.split('.')

Possible solution

With the help of a new boolean parameter (e.g splitKey or noSplit) for the Localization::findSentence() method, if an error occurs, we should fallback to const parts = [key] instead of const parts = key.split('.') via the splitKey or noSplit flag parameter.

Here a possible patch (need some tests) for the src/js/Localization/core.ts file:

class Localization {
 //...

 /**
   * Find the sentence using associated with the [key].
   * @param key
   * @param silentNotFoundError
   * @param locale
   * @param splitKey
   * @returns {string}
   * @private
   */
  private findSentence(key: string, silentNotFoundError: boolean, locale: string = MaticeLocalizationConfig.locale, splitKey: boolean = true): string {
    const translations: { [key: string]: any } = this.translations(locale)

    // At first [link] is a [Map<String, dynamic>] but at the end, it can be a [String],
    // the sentences.
    let link = translations

    const parts = splitKey ? key.split('.') : [key]

    for (const part of parts) {
      // Get the new json until we fall on the last key of
      // the array which should point to a String.
      try {
        // Make sure the _key exist.
        // If not this throws an error that is handled by the "catch" block
        // @ts-ignore
        assert(link[part])
        // @ts-ignore
        link = link[part]
      } catch (e) {
        // If key not found, try without splitting it.
        if (splitKey) {
          return this.findSentence(key, silentNotFoundError, locale, false)
        }

        // If key not found, try with the fallback locale.
        if (locale !== MaticeLocalizationConfig.fallbackLocale) {
          return this.findSentence(key, silentNotFoundError, MaticeLocalizationConfig.fallbackLocale, splitKey)
        }

        // If the key not found and the silent mode is on, return the key,
        if (silentNotFoundError) return key;

        // If key not found and the silent mode is off, throw error,
        throw `Translation key not found : "${key}" -> Exactly "${part}" not found`
      }
    }

    key.split('.').forEach((_key) => {

    });

    return link.toString();
  }
}

Documentation update @translations

When including @translations you must add locale as custom blade directive param.

@translations(app()->getLocale())

This however unnecessary, I think it should be inside the custom blade directive callback function.

Export statement missing from generated file

Expected behavior

The the line below as per documentation would appear at the end of the generated translations file:

export { Matice };

Current behavior

This line is missing from the \Genl\Matice\Commands\TranslationsGeneratorCommand::handle() method. As a result this line would have to be manually entered every time if one is to compile the resultant file as part of their JS deployment.

Versions

  • Laravel: 8.34.0
  • Matice: 1.1.6

Blade directive markup

Expected behavior

<!-- Matice Laravel Translations generated -->
<script>
  const Matice = {
    locale: "en",
    fallbackLocale: "en",
    translations: { ... }
  }
</script>

Current behavior

<!-- Matice Laravel Translations generated -->
<div id="matice-translations">
  <script type="text/javascript">
    const Matice = {
      locale: "en",
      fallbackLocale: "en",
      translations: { ... }
    }
  </script>
</div>

Reasoning

  • The div#matice-translations seems excessive and <div>s are not supported within the <head> forcing the directive into the <body>. Not that this is a big problem, but having the option to put it in <head> would be nice.

  • This is just a very minor nuisance, but in HTML5 it is recommended to omit [type="text/javascript"] from <script>. Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

Versions

  • Laravel: 8.15.0
  • Matice: 1.1.2

Dynamic namespace filtering

Expected behavior

Dynamic/middleware-based namespace filtering. For example, being able to filter which translations are sent depending on the current route.

There are a lot of translations in the admin panel of my app, and they aren't needed for the user front-end. I can exclude them using namespace filters, but I can't do it selectively based on the route.

I'd like a way to only send some translations for my public pages, but all of the translations for the admin pages.

Current behavior

Possibly a modification of the current namespace filtering?

Labels don't update until matice_translations.js deletion

Expected behavior

[PRODUCTION ENV] I've created a fully working locale selector that does another action in addition to calling setLocale(locale). The expected behavior is that when i refresh the page (after changing the locale), i want to see the updated labels, localized in the selected language.

Current behavior

[PRODUCTION ENV] When i change the locale, my locale selector works fine, it does call setLocale(locale) and does what i want it to do, but the labels don't change. To make them change, i need to delete matice_translations.js

Versions

  • Laravel: v8.60.0
  • Matice: 1.1.6

Translations

['greet' => 'It greets the wrong way.']

trans('greet') // Something strange.

Contents of Matice.translations

const Matice = {
  locale: 'it',
  fallbackLocale: 'en',
  translations: {"en":{"addFriends":{"title":"Add friends","search-ph":"Enter at least 3 characters of a user's username","users-found":"Users found","no-users":"No users found","received-requests":"Received requests","friends":"Friends","sent-requests":"Sent requests","messages":{"user-not-found":"User not found","added-friend":"Friend added successfully!","request-sent":"Request sent","already-in-touch":"You're already in touch!","reply-sent":"Reply sent successfully!","removed-friend":"Friend removed"},"user-card":{"reject":"Reject","accept":"Accept","friends":"Friends","request-sent":"Request sent","request-received":"Request received","request-rejected":"Request rejected","add":"Add"}},"app":{"name":"Let's","motto":"Together's better","error-title":"Oops, something went wrong","nav":{"home":"Home","add-friends":"Add friends"},"locales":{"en":"English","it":"Italiano"},"account-menu":{"manage":"Manage account","profile":"Profile","thoughts":"Thoughts","friends":"Friends","notifications":"Notifications","profile-settings":"Settings","api":"API credentials","logout":"Logout"}},"auth":{"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.","login":{"title":"Login","email-or-username":"Email or Username","password":"Password","remember-me":"Remember me","forgot-password":"Forgot your password?","login":"Login","register":"Not registered yet? <strong><a href=\":url\">Register now!<\/a><\/strong>","2fa-title":"Two-Factor Authentication","2fa-code":"Auth code","2fa-code-text":"Please confirm access to your account by entering the authentication code provided by your authenticator application.","2fa-use-recovery-code":"Use recovery code","2fa-recovery-code":"Recovery code","2fa-recovery-code-text":"Please confirm access to your account by entering one of your emergency recovery codes.","2fa-use-code":"Use auth code"},"forgot-password":{"title":"Forgot password","text":"Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.","email":"Email","send":"Send link"},"verify-email":{"title":"Email Verification","text":"Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.","new-sending":"A new verification link has been sent to the email address you provided during registration.","resend":"Resend Verification Email","logout":"Logout"},"register":{"title":"Register","username":"Username","email":"Email","password":"Password","confirm-password":"Confirm password","read-policies":"I agree to the <a target=\"_blank\" href=\":termsUrl\" class=\"underline text-sm text-gray-600 hover:text-gray-900\">Terms of Service<\/a> and <a target=\"_blank\" href=\":privacyUrl\" class=\"underline text-sm text-gray-600 hover:text-gray-900\">Privacy Policy<\/a>","register":"Register","login":"Already registered?"},"reset-password":{"email":"Email","password":"Password","confirm-password":"Confirm password","reset-password":"Reset password"}},"friends":{"title":"Friends","friends":"Friends","no-friends":"No friends yet","friends-for":"Friends for","less-than-day":"less than a day","days":"[0,1]day|[2,*]days","months":"[0,1]month|[2,*]months","years":"[0,1]year|[2,*]years"},"notifications":{"title":"Notifications","no-notifications":"No notifications","load-more":"Load more","friendships":{"new-friend":"<strong>@:username<\/strong> added you as a friend!","new-friend-telegram":"**:username** added you as a friend!","new-friend-request":"<strong>@:username<\/strong> sent you a friend request","new-friend-request-telegram":"**:username** sent you a friend request","request-accepted":"<strong>@:username<\/strong> accepted your friend request!","request-accepted-telegram":"**:username** accepted your friend request!","go-to-profile":"Go to profile"}},"pagination":{"previous":"&laquo; Previous","next":"Next &raquo;"},"passwords":{"reset":"Your password has been reset!","sent":"We have emailed your password reset link!","throttled":"Please wait before retrying.","token":"This password reset token is invalid.","user":"We can't find a user with that email address."},"profile":{"title":"Profile","informations":{"title":"Informations","text":"Update your account's profile information and email address.","photo":"Profile photo","select-new-photo":"Change profile photo","remove-photo":"Remove profile photo","name":"Name","username":"Username","email":"Email"},"bio":{"title":"Bio","text":"Update your bio"},"phone":{"title":"Phone","text":"Update your account's phone number."},"password":{"title":"Update password","text":"Ensure your account is using a long, random password to stay secure.","current":"Current password","new":"New password","confirm":"Confirm new password"},"2fa":{"title":"Two Factor Authentication","text":"Add additional security to your account using two factor authentication.","not-enabled-title":"You have not enabled two factor authentication.","not-enabled-text":"When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.","enable":"Enable","enabled-title":"You have enabled two factor authentication.","enabled-text":"When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve this token from your phone's Google Authenticator application.","qr-text":"Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application.","recovery-codes-text":"Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.","recovery-codes-show":"Show recovery codes","recovery-codes-regenerate":"Regenerate recovery codes","disable":"Disable"},"sessions":{"title":"Sessions","text":"Manage and log out your active sessions on other browsers and devices.","logout-text":"If necessary, you may log out of all of your other browser sessions across all of your devices. Some of your recent sessions are listed below; however, this list may not be exhaustive. If you feel your account has been compromised, you should also update your password.","logout-button":"Logout other browser sessions","this-device":"This device","last-active":"Last active","confirm-text":"Please enter your password to confirm you would like to log out of your other browser sessions across all of your devices.","password":"Password","cancel":"Cancel","done":"Done"},"delete":{"title":"Delete account","text":"Permanently delete your account.","delete-text":"Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.","button":"Delete account","password":"Password","cancel":"Cancel","confirm-text":"Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.        "},"settings":{"title":"Settings - Profile","locale-title":"Localization","locale-text":"Choose your language","visibility-title":"Visibility","visibility-text":"You have full control over the visibility of your profile and all of its parts","profile-visibility-title":"Profile","profile-visibility-text":"Choose to make your profile private or public","email-visibility-title":"Email","email-visibility-text":"Choose to make your email visible on your profile","bio-visibility-title":"Bio","bio-visibility-text":"Choose to make your bio visible on your profile","phone-visibility-title":"Phone","phone-visibility-text":"Choose to make your phone visible on your profile","public":"Public","private":"Private","visible":"Visible","hidden":"Hidden","telegram-title":"Telegram","telegram-text":"Enter your Telegram ID to receive your notifications on Telegram","telegram-id-title":"Telegram ID","telegram-id-text":"Click on the following link to start the conversation with our official bot. Once the conversation starts, the bot will tell you what your ID is. After that, you can copy and paste it into the field below","privacy-title":"Privacy","privacy-text":"You also have full control over your privacy settings"},"confirms-password":{"cancel":"Cancel","title":"Confirm Password","content":"For your security, please confirm your password to continue.","button":"Confirm"},"messages":{"update-profile":{"success":"Profile updated correctly","error":"There was an error, try again later"},"bio-updated":"Bio updated","phone-updated":"Phone updated","visibility-updated":"Visibility updated","privacy-updated":"Privacy updated","telegram-updated":"Telegram ID updated"},"save":"Save","saved":"Saved"},"thoughts":{"title":"Thoughts","thoughts":"Thoughts","add-tought-ph":"Write down your thought to share it with others...","no-thoughts":"No thoughts yet","load-more":"Load more","publish":"Publish","messages":{"thought-added":"Thought added successfully!"}},"user":{"friends-for":"Friends for","request-already-sent":"You've sent a request to <strong class=\"text-primary\">@:username<\/strong>","answer-request":"Answer <strong class=\"text-primary\">@:username<\/strong>'s request","revert-request-text":"You've rejected <strong class=\"text-primary\">@:username<\/strong>'s request, but you can think again whenever you want by clicking on the button below","revert-request-button":"Add @:username to your friends","remove-friend":"Remove friend","remove-confirm-text":"Are you sure you want to remove <span class=\"font-bold mt-3 text-primary\">@:username<\/span> from your friends?","remove-friend-cancel-button":"No, I have second thoughts","remove-friend-confirm-button":"Yes, remove from friends","remove":"Remove from friends","reject":"Reject","accept":"Accept"},"validation":{"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":{"numeric":"The :attribute must be between :min and :max.","file":"The :attribute must be between :min and :max kilobytes.","string":"The :attribute must be between :min and :max characters.","array":"The :attribute must have between :min and :max items."},"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.","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.","exists":"The selected :attribute is invalid.","file":"The :attribute must be a file.","filled":"The :attribute field must have a value.","gt":{"numeric":"The :attribute must be greater than :value.","file":"The :attribute must be greater than :value kilobytes.","string":"The :attribute must be greater than :value characters.","array":"The :attribute must have more than :value items."},"gte":{"numeric":"The :attribute must be greater than or equal :value.","file":"The :attribute must be greater than or equal :value kilobytes.","string":"The :attribute must be greater than or equal :value characters.","array":"The :attribute must have :value items or more."},"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":{"numeric":"The :attribute must be less than :value.","file":"The :attribute must be less than :value kilobytes.","string":"The :attribute must be less than :value characters.","array":"The :attribute must have less than :value items."},"lte":{"numeric":"The :attribute must be less than or equal :value.","file":"The :attribute must be less than or equal :value kilobytes.","string":"The :attribute must be less than or equal :value characters.","array":"The :attribute must not have more than :value items."},"max":{"numeric":"The :attribute must not be greater than :max.","file":"The :attribute must not be greater than :max kilobytes.","string":"The :attribute must not be greater than :max characters.","array":"The :attribute must not have more than :max items."},"mimes":"The :attribute must be a file of type: :values.","mimetypes":"The :attribute must be a file of type: :values.","min":{"numeric":"The :attribute must be at least :min.","file":"The :attribute must be at least :min kilobytes.","string":"The :attribute must be at least :min characters.","array":"The :attribute must have at least :min items."},"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":"The password is incorrect.","present":"The :attribute field must be present.","regex":"The :attribute format is invalid.","required":"The :attribute field is required.","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.","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.","same":"The :attribute and :other must match.","size":{"numeric":"The :attribute must be :size.","file":"The :attribute must be :size kilobytes.","string":"The :attribute must be :size characters.","array":"The :attribute must contain :size items."},"starts_with":"The :attribute must 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":{"attribute-name":{"rule-name":"custom-message"}},"attributes":[]}},"it":{"addFriends":{"title":"Aggiungi amici","search-ph":"Inserisci almeno 3 caratteri dello username di un utente","users-found":"Utenti trovati","no-users":"Nessun utente trovato","received-requests":"Richieste ricevute","friends":"Amici","sent-requests":"Richieste inviate","messages":{"user-not-found":"Utente non trovato","added-friend":"Amico aggiunto con successo!","request-sent":"Richiesta inviata","already-in-touch":"Siete gi\u00e0 in contatto!","reply-sent":"Risposta inviata con successo!","removed-friend":"Amico rimosso"},"user-card":{"reject":"Rifiuta","accept":"Accetta","friends":"Amici","request-sent":"Richiesta inviata","request-received":"Richiesta ricevuta","request-rejected":"Richiesta rifiutata","add":"Aggiungi"}},"app":{"name":"Let's","motto":"Insieme \u00e8 meglio","error-title":"Ops, qualcosa \u00e8 andato storto","nav":{"home":"Home","add-friends":"Aggiungi amici"},"locales":{"en":"English","it":"Italiano"},"account-menu":{"manage":"Gestisci account","profile":"Profilo","thoughts":"Pensieri","friends":"Amici","notifications":"Notifiche","profile-settings":"Impostazioni","api":"Credenziali API","logout":"Esci"}},"auth":{"failed":"Credenziali errate","password":"Password errata","throttle":"Troppi tentativi di accesso. Per favore riprova in :seconds secondi","login":{"title":"Accedi","email-or-username":"Email o Username","password":"Password","remember-me":"Ricordami","forgot-password":"Password dimenticata?","login":"Accedi","register":"Non hai ancora un account? <strong><a href=\":url\">Registrati ora!<\/a><\/strong>","2fa-title":"Autenticazione a 2 fattori","2fa-code":"Codice di autenticazione","2fa-code-text":"Conferma l'accesso al tuo account inserendo il codice di autenticazione fornito dalla tua app authenticator","2fa-use-recovery-code":"Usa recovery code","2fa-recovery-code":"Recovery code","2fa-recovery-code-text":"Conferma l'accesso al tuo account inserendo uno dei tuoi recovery code di emergenza","2fa-use-code":"Usa codice di autenticazione"},"forgot-password":{"title":"Password dimenticata","text":"Password dimenticata? Nessun problema! Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.","email":"Email","send":"Invia link"},"verify-email":{"title":"Verifica email","text":"Grazie per esserti registrato! Prima di iniziare, potresti verificare la tua email cliccando sul link che ti abbiamo inviato? Se non l'hai ricevuto, te ne mandiamo un altro volentieri!","new-sending":"Un nuovo link di verifica \u00e8 stato inviato all'indirizzo email che ci hai fornito in fase di registrazione","resend":"Invia nuova email di verifica","logout":"Esci"},"register":{"title":"Registrati","username":"Username","email":"Email","password":"Password","confirm-password":"Conferma password","read-policies":"Acconsento ai <a target=\"_blank\" href=\":termsUrl\" class=\"underline text-sm text-gray-600 hover:text-gray-900\">Termini di utilizzo<\/a> e alla <a target=\"_blank\" href=\":privacyUrl\" class=\"underline text-sm text-gray-600 hover:text-gray-900\">Privacy Policy<\/a>","register":"Registrati","login":"Sei gi\u00e0 registrato?"},"reset-password":{"email":"Email","password":"Password","confirm-password":"Conferma password","reset-password":"Reimposta password"},"confirm-password":{"title":"Area sicura","text":"Questa \u00e8 un'area sicura dell'applicazione. Per favore, conferma la tua password per continuare","password":"Password","confirm":"Conferma"}},"friends":{"title":"Amici","friends":"Amici","no-friends":"Ancora nessun amico","friends-for":"Amici da","less-than-day":"meno di un giorno","days":"[0,1]giorno|[2,*]giorni","months":"[0,1]mese|[2,*]mesi","years":"[0,1]anno|[2,*]anni"},"notifications":{"title":"Notifiche","no-notifications":"Nessuna notifica","load-more":"Carica altre","friendships":{"new-friend":"<strong>@:username<\/strong> ti ha aggiunto come amico!","new-friend-telegram":"**:username** ti ha aggiunto come amico!","new-friend-request":"<strong>@:username<\/strong> ti ha inviato una richiesta di amicizia","new-friend-request-telegram":"**:username** ti ha inviato una richiesta di amicizia","request-accepted":"<strong>@:username<\/strong> ha accettato la tua richiesta di amicizia!","request-accepted-telegram":"**:username** ha accettato la tua richiesta di amicizia!","go-to-profile":"Vai al profilo"}},"passwords":{"reset":"Hai reimpostato la tua password!","sent":"Ti abbiamo inviato un link per reimpostare la password, controlla la tua email","throttled":"Ti preghiamo di attendere, prima di riprovare","token":"Token non valido","user":"Utente inesistente"},"profile":{"title":"Profilo","informations":{"title":"Informazioni","text":"Aggiorna le informazioni del tuo profilo","photo":"Foto profilo","select-new-photo":"Cambia foto profilo","remove-photo":"Rimuovi foto profilo","name":"Nome","username":"Username","email":"Email"},"bio":{"title":"Bio","text":"Aggiorna la tua bio"},"phone":{"title":"Telefono","text":"Aggiorna il tuo numero di telefono"},"password":{"title":"Aggiorna password","text":"Assicurati di utilizzare password lunghe e sicure","current":"Password attuale","new":"Nuova password","confirm":"Conferma nuova password"},"2fa":{"title":"Autenticazione a 2 fattori","text":"Aggiungi un livello di sicurezza abilitando l'autenticazione a 2 fattori","not-enabled-title":"Non hai abilitato l'autenticazione a 2 fattori","not-enabled-text":"Quando l'autenticazione a 2 fattori \u00e8 abilitata, in fase di accesso ti verr\u00e0 richiesto di inserire un token sicuro. Puoi recuperare tale token nella tua app authenticator (Google o altre)","enable":"Abilita","enabled-title":"Hai abilitato l'autenticazione a 2 fattori","enabled-text":"Quando l'autenticazione a 2 fattori \u00e8 abilitata, in fase di accesso ti verr\u00e0 richiesto di inserire un token sicuro. Puoi recuperare tale token nella tua app authenticator (Google o altre)","qr-text":"L'autenticazione a 2 fattori \u00e8 abilitata. Scansiona il seguente codice QR nella tua app authenticator","recovery-codes-text":"Conserva questi recovery code in un posto sicuro. Questi potranno essere usati per recuperare l'accesso al tuo account nel caso in cui perdessi i dati della tua app authenticator","recovery-codes-show":"Mostra recovery codes","recovery-codes-regenerate":"Rigenera recovery codes","disable":"Disabilita"},"sessions":{"title":"Sessioni","text":"Gestisci e scollega le tue sessioni attive su altri browser e dispositivi","logout-text":"Se necessario, puoi effettuare il logout da tutte le altre sessioni tra tutti i tuoi dispositivi. Alcune delle tue sessioni recenti sono elencate qui sotto; comunque la lista potrebbe non essere esaustiva. Se ritieni che il tuo account sia stato compromesso, ti consigliamo di aggiornare anche la password","logout-button":"Effettua il logout da tutte le altre sessioni","this-device":"Questo dispositivo","last-active":"Ultima attivit\u00e0","confirm-text":"Inserisci la tua password per confermare che vuoi effettuare il logout da tutte le altre sessioni su tutti i tuoi dispositivi","password":"Password","cancel":"Annulla","done":"Fatto"},"delete":{"title":"Elimina account","text":"Elimina permanentemente il tuo account","delete-text":"Una volta che il tuo account sar\u00e0 eliminato, tutte le risorse ed i dati ad esso associati saranno eliminati permanentemente. Prima di eliminare il tuo account, ti preghiamo di salvare i dati che hai intenzione di conservare","button":"Elimina account","password":"Password","cancel":"Annulla","confirm-text":"Sei sicuro di voler eliminare il tuo account? Una volta che il tuo account sar\u00e0 eliminato, tutte le risorse ed i dati ad esso associati saranno eliminati permanentemente. Inserisci la tua password per confermare che vuoi eliminare il tuo account permanentemente"},"settings":{"title":"Impostazioni - Profilo","locale-title":"Lingua","locale-text":"Scegli la tua lingua","visibility-title":"Visibilit\u00e0","visibility-text":"Hai pieno controllo sulla visibilit\u00e0 del tuo profilo e di tutte le sue parti","profile-visibility-title":"Profilo","profile-visibility-text":"Scegli se rendere il tuo profilo privato o pubblico","email-visibility-title":"Email","email-visibility-text":"Scegli se rendere la tua email visibile sul tuo profilo","bio-visibility-title":"Bio","bio-visibility-text":"Scegli se rendere la tua bio visibile sul tuo profilo","phone-visibility-title":"Telefono","phone-visibility-text":"Scegli se rendere il tuo telefono visibile sul tuo profilo","public":"Pubblico","private":"Privato","visible":"Visibile","hidden":"Nascosto","telegram-title":"Telegram","telegram-text":"Inserisci il tuo ID Telegram per ricevere le tue notifiche su Telegram","telegram-id-title":"ID Telegram","telegram-id-text":"Clicca sul seguente link per iniziare la conversazione con il nostro bot ufficiale. Una volta avviata la conversazione, il bot ti dir\u00e0 qual \u00e8 il tuo ID. Dopodich\u00e9, potrai copiarlo e incollarlo nel campo qui sotto","privacy-title":"Privacy","privacy-text":"Hai anche il pieno controllo sulle impostazioni della privacy"},"confirms-password":{"cancel":"Annulla","title":"Conferma password","content":"Per la tua sicurezza, per favore, conferma la tua password per continuare","button":"Conferma"},"messages":{"update-profile":{"success":"Profilo aggiornato correttamente","error":"Si \u00e8 verificato un errore, riprova pi\u00f9 tardi"},"bio-updated":"Bio aggiornata","phone-updated":"Telefono aggiornato","visibility-updated":"Visibilit\u00e0 aggiornate","privacy-updated":"Privacy aggiornate","telegram-updated":"ID Telegram aggiornato"},"save":"Salva","saved":"Salvato"},"thoughts":{"title":"Pensieri","thoughts":"Pensieri","add-tought-ph":"Scrivi un tuo pensiero per condividerlo con altri...","no-thoughts":"Ancora nessun pensiero","load-more":"Carica altri","publish":"Pubblica","messages":{"thought-added":"Pensiero aggiunto con successo!"}},"user":{"friends-for":"Amici da","request-already-sent":"Hai inviato una richiesta di amicizia a <strong class=\"text-primary\">@:username<\/strong>","answer-request":"Rispondi alla richiesta di <strong class=\"text-primary\">@:username<\/strong>","revert-request-text":"Hai rifiutato la richiesta di amicizia di <strong class=\"text-primary\">@:username<\/strong>, ma puoi ripensarci quando vuoi cliccando sul bottone qui sotto","revert-request-button":"Aggiungi @:username agli amici","remove-friend":"Rimuovi amico","remove-confirm-text":"Sei sicuro di voler rimuovere <span class=\"font-bold mt-3 text-primary\">@:username<\/span> dagli amici?","remove-friend-cancel-button":"No, ci ho ripensato","remove-friend-confirm-button":"Si, rimuovi dagli amici","remove":"Rimuovi dagli amici","reject":"Rifiuta","accept":"Accetta"},"validation":{"accepted":":attribute deve essere accettato.","accepted_if":":attribute deve essere accettato quando :other \u00e8 :value.","active_url":":attribute non \u00e8 un URL valido.","after":":attribute deve essere una data successiva al :date.","after_or_equal":":attribute deve essere una data successiva o uguale al :date.","alpha":":attribute pu\u00f2 contenere solo lettere.","alpha_dash":":attribute pu\u00f2 contenere solo lettere, numeri e trattini.","alpha_num":":attribute pu\u00f2 contenere solo lettere e numeri.","array":":attribute deve essere un array.","attached":":attribute \u00e8 gi\u00e0 associato.","before":":attribute deve essere una data precedente al :date.","before_or_equal":":attribute deve essere una data precedente o uguale al :date.","between":{"array":":attribute deve avere tra :min - :max elementi.","file":":attribute deve trovarsi tra :min - :max kilobyte.","numeric":":attribute deve trovarsi tra :min - :max.","string":":attribute deve trovarsi tra :min - :max caratteri."},"boolean":"Il campo :attribute deve essere vero o falso.","confirmed":"Il campo di conferma per :attribute non coincide.","current_password":"Password non valida.","date":":attribute non \u00e8 una data valida.","date_equals":":attribute deve essere una data e uguale a :date.","date_format":":attribute non coincide con il formato :format.","different":":attribute e :other devono essere differenti.","digits":":attribute deve essere di :digits cifre.","digits_between":":attribute deve essere tra :min e :max cifre.","dimensions":"Le dimensioni dell'immagine di :attribute non sono valide.","distinct":":attribute contiene un valore duplicato.","email":":attribute non \u00e8 valido.","ends_with":":attribute deve finire con uno dei seguenti valori: :values","exists":":attribute selezionato non \u00e8 valido.","file":":attribute deve essere un file.","filled":"Il campo :attribute deve contenere un valore.","gt":{"array":":attribute deve contenere pi\u00f9 di :value elementi.","file":":attribute deve essere maggiore di :value kilobyte.","numeric":":attribute deve essere maggiore di :value.","string":":attribute deve contenere pi\u00f9 di :value caratteri."},"gte":{"array":":attribute deve contenere un numero di elementi uguale o maggiore di :value.","file":":attribute deve essere uguale o maggiore di :value kilobyte.","numeric":":attribute deve essere uguale o maggiore di :value.","string":":attribute deve contenere un numero di caratteri uguale o maggiore di :value."},"image":":attribute deve essere un'immagine.","in":":attribute selezionato non \u00e8 valido.","in_array":"Il valore del campo :attribute non esiste in :other.","integer":":attribute deve essere un numero intero.","ip":":attribute deve essere un indirizzo IP valido.","ipv4":":attribute deve essere un indirizzo IPv4 valido.","ipv6":":attribute deve essere un indirizzo IPv6 valido.","json":":attribute deve essere una stringa JSON valida.","lt":{"array":":attribute deve contenere meno di :value elementi.","file":":attribute deve essere minore di :value kilobyte.","numeric":":attribute deve essere minore di :value.","string":":attribute deve contenere meno di :value caratteri."},"lte":{"array":":attribute deve contenere un numero di elementi minore o uguale a :value.","file":":attribute deve essere minore o uguale a :value kilobyte.","numeric":":attribute deve essere minore o uguale a :value.","string":":attribute deve contenere un numero di caratteri minore o uguale a :value."},"max":{"array":":attribute non pu\u00f2 avere pi\u00f9 di :max elementi.","file":":attribute non pu\u00f2 essere superiore a :max kilobyte.","numeric":":attribute non pu\u00f2 essere superiore a :max.","string":":attribute non pu\u00f2 contenere pi\u00f9 di :max caratteri."},"mimes":":attribute deve essere del tipo: :values.","mimetypes":":attribute deve essere del tipo: :values.","min":{"array":":attribute deve avere almeno :min elementi.","file":":attribute deve essere almeno di :min kilobyte.","numeric":":attribute deve essere almeno :min.","string":":attribute deve contenere almeno :min caratteri."},"multiple_of":":attribute deve essere un multiplo di :value","not_in":"Il valore selezionato per :attribute non \u00e8 valido.","not_regex":"Il formato di :attribute non \u00e8 valido.","numeric":":attribute deve essere un numero.","password":"Il campo :attribute non \u00e8 corretto.","present":"Il campo :attribute deve essere presente.","prohibited":":attribute non consentito.","prohibited_if":":attribute non consentito quando :other \u00e8 :value.","prohibited_unless":":attribute non consentito a meno che :other sia contenuto in :values.","prohibits":":attribute impedisce a :other di essere presente.","regex":"Il formato del campo :attribute non \u00e8 valido.","relatable":":attribute non pu\u00f2 essere associato a questa risorsa.","required":"Il campo :attribute \u00e8 richiesto.","required_if":"Il campo :attribute \u00e8 richiesto quando :other \u00e8 :value.","required_unless":"Il campo :attribute \u00e8 richiesto a meno che :other sia in :values.","required_with":"Il campo :attribute \u00e8 richiesto quando :values \u00e8 presente.","required_with_all":"Il campo :attribute \u00e8 richiesto quando :values sono presenti.","required_without":"Il campo :attribute \u00e8 richiesto quando :values non \u00e8 presente.","required_without_all":"Il campo :attribute \u00e8 richiesto quando nessuno di :values \u00e8 presente.","same":":attribute e :other devono coincidere.","size":{"array":":attribute deve contenere :size elementi.","file":":attribute deve essere :size kilobyte.","numeric":":attribute deve essere :size.","string":":attribute deve contenere :size caratteri."},"starts_with":":attribute deve iniziare con uno dei seguenti: :values","string":":attribute deve essere una stringa.","timezone":":attribute deve essere una zona valida.","unique":":attribute \u00e8 stato gi\u00e0 utilizzato.","uploaded":":attribute non \u00e8 stato caricato.","url":"Il formato del campo :attribute non \u00e8 valido.","uuid":":attribute deve essere un UUID valido.","custom":{"attribute-name":{"rule-name":"custom-message"}}}}}
}

Translations with arguments in templates as syntax errors

Problem

<template>
    <div>{{ $trans("pagination.showing", {args: { to: "hello" }}) }}</div>
</template>

This syntax generates a compiling error because of the nested curly braces:
VueCompilerError: Error parsing JavaScript expression: Unexpected token, expected "," (1:50)

Suggestion

New syntax for $trans, and $__ (breaking change)

$trans('translation', {args1: 'value', args2: 'value'}, {options})
This syntax would make much more sense, be less cluttered and would not rise any compling errors. I understand this is a breaking change, so maybe you can add another function instead.

Create new $newtrans, and $new__ functions

The definition is as simple as:
const $newtrans= (key, args = {}, options = {}) => __(key, { args, ...options });
const $new__= (key, args = {}, options = {}) => __(key, { args, ...options });

Usage example

{{
$newtrans("pagination.showing",
                     { from: firstItem, to: lastItem, count: total, },
                     { pluralize: true }
                 )
}}

This raises no compilation errors.

Exposing paths

Expected behavior

Remove HTML comments from production build at least.

Current behavior

Comments injected

Matice Laravel Translations generated
Used cached translations at: /home/forge/xxx/resources/assets/js/matice_translations.js 

I think it's giving everyone additional unesesery information and should be removed.

Settings publish doesn't work

Expected behavior

Settings published to config dir

Current behavior

php artisan vendor:publish --provider=Genl\Matice\MaticeServiceProvider
Unable to locate publishable resources.

Versions

  • Laravel: 8.33.1
  • Matice: 1.1.6

Failed to open stream: No such file or directory - only on server

file_put_contents(/var/www/staging/releases/13/resources/assets/js/matice_translations.js): Failed to open stream: No such file or directory (View: /var/www/staging/releases/13/resources/views/layouts/head.blade.php)

Do you have perhaps any idea why I'm not able to use it on my server? I get the above error. On my local machine, everything works fine 😅

I'm unfortunately on a hurray but I'm going to dig deeper into what the problem is. Maybe you are faster :D

Laravel 8 - Vue3 Matice is not defined.

Hello,

i am using laravel8 with vue3.
I installed matice but i got this error "Matice is not defined" on app.js when i render my page.

my app code:

import {__, trans, setLocale, getLocale, transChoice, MaticeLocalizationConfig, locales} from "matice"


const el = document.getElementById('app');

createApp({
    render: () =>
        h(InertiaApp, {
            initialPage: JSON.parse(el.dataset.page),
            resolveComponent: (name) => require(`./Pages/${name}`).default,
        }),
})
    .mixin({ methods: { route,
            $trans: trans,
            $_: _,
            $transChoice: transChoice,
            $setLocale(locale) {
                setLocale(locale);
                this.$forceUpdate() // Refresh the vue instance(The whole app in case of SPA) after the locale changes.
            },
            // The current locale
            $locale() {
                return getLocale()
            },
            // A listing of the available locales
            $locales() {
                return locales()
            }
    } })
    .use(InertiaPlugin)
    .mount(el);

InertiaProgress.init({ color: '#4B5563' });

The command "matice:generate" does not exist.

When I deployed my application to production for the first time I received this error:

[2021-03-19 17:37:21] production.ERROR: The command "matice:generate" does not exist. (View: /home/forge/staging.xxxxx.com/releases/1/resources/views/app.blade.php) {"exception":"[object] (Illuminate\\View\\ViewException(code: 0): The command \"matice:generate\" does not exist. (View: /home/forge/staging.xxxxx.com/releases/1/resources/views/app.blade.php) at /home/forge/staging.xxxxx.com/releases/1/vendor/laravel/framework/src/Illuminate/Console/Application.php:180)

The command actually exists and I was able to run it on the server to generate the translations.js file.

Any idea what I might be missing?

Versions

  • Laravel: 8.33.1
  • Matice: 1.1.5

Js or Json per language and lazy loading

I saw your comment on PR discussion of Laravel Jetstream. I'm still looking for an I18n solution for my project. It should be realized with Laravel/Jetstream/Inertia/Vue. I studied your solution.

Your library is very good because it uses Laravel's standard files and standard location.

As I understand your component is loading all languages together with the blade directive. Same with the Artisan command which produces one file together with current local, fallback and all translations. This is OK for small applications with few languages. But think about an application with 20 - 50 pages and 3 to 10 languages? One user uses one language only mostly.

My feature request is:

  • The Artisan command should generate one file per language
  • The blade directive should send one language only for current locale. Often nobody is changing his standard language.
  • A provided Laravel Middleware component should send the value of App::currentLocale and fallback locale to Inertia shared which can used in app.js to set locale (via function setLocale)
  • The setLocale function should check if the translations for requested local is already loaded. If not import with lazy loading see function loadLanguageAsync of Vue I18n
  • Replace the lang attribute in HTML tag with current locale in function setLocale
  • When using Inertia's shared for current locale then "setLocale" could executed for every response. This allows the developer to change the language via route (change with App:setLocale) or while storing user's language etc.

What are you thinking? Thank you very much for your answer.

Not detecting locale change

Not sure what's a point to cache matice_translations.js in production. This file should be regenerated at least when switching locale, because now when we first visit the site, it generates:

locale: 'en',
fallbackLocale: 'en',

And that is, you stuck in EN version even if you change the locale.

And it's ignoring:

'use_generated_translations_file_in_prod' => false,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.