Rupert  Beatty

Rupert Beatty

1672960080

Lingo-Vapor: Vapor Provider for Lingo - The Swift Localization Library

Lingo Provider

A Vapor provider for Lingo - a pure Swift localization library ready to be used in Server Side Swift projects.

Setup

Add a dependancy

Add LingoProvider as a dependancy in your Package.swift file:

dependencies: [
    ...,
    .package(name: "LingoVapor", url: "https://github.com/vapor-community/Lingo-Vapor.git", from: "4.2.0")]
],
targets: [
    .target(name: "App", dependencies: [
        .product(name: "LingoVapor", package: "Lingo-Vapor")

Upgrading from version 4.1.0 to version 4.2.0

The version 4.1.0 uses the new version of Lingo where the format of locale identifiers was changed to match RFC 5646. Prior to 4.2.0 _ was used to separate language code and country code in the locale identifier, and now the library uses - as per RFC.

If you were using any locales which include a country code, you would need to rename related translation files to match the new format.

Add the Provider

In the configure.swift simply initialize the LingoVapor with a default locale:

import LingoVapor
...
public func configure(_ app: Application) throws {
    ...
    app.lingoVapor.configuration = .init(defaultLocale: "en", localizationsDir: "Localizations")
}

The localizationsDir can be omitted, as the Localizations is also the default path. Note that this folder should exist under the workDir.

Use

After you have configured the provider, you can use lingoVapor service to create Lingo:

let lingo = try app.lingoVapor.lingo()
...
let localizedTitle = lingo.localize("welcome.title", locale: "en")

To get the locale of a user out of the request, you can use request.locale. This uses a language, which is in the HTTP header and which is in your available locales, if that exists. Otherwise it falls back to the default locale. Now you can use different locales dynamically:

let localizedTitle = lingo.localize("welcome.title", locale: request.locale)

When overwriting the requested locale, just write the new locale into the session, e.g. like that:

session.data["locale"] = locale

Use the following syntax for defining localizations in a JSON file:

{
    "title": "Hello Swift!",
    "greeting.message": "Hi %{full-name}!",
    "unread.messages": {
        "one": "You have one unread message.",
        "other": "You have %{count} unread messages."
    }
}

Locale redirection middleware

In case you want to serv different locales on different subfolders, you can use the LocaleRedirectMiddleware.

Add in configure.swift:

import LingoVapor

// Inside `configure(_ app: Application)`:
app.middleware.use(LocaleRedirectMiddleware())

Add in routes.swift:

import LingoVapor

// Inside `routes(_ app: Application)`:
app.get("home") { /* ... */ }
app.get(":locale", "home") { /* ... */ } // For each route, add the one prefixed by the `locale` parameter

That way, going to /home/ will redirect you to /<locale>/home/ (with <locale> corresponding to your browser locale), and going to /fr/home/ will display homepage in french whatever the browser locale is.

Inside Leaf templates

When using Leaf as templating engine, you can use LocalizeTag, LocaleTag and LocaleLinksTag from LingoVaporLeaf for localization inside the templates.

Add in configure.swift:

import LingoVaporLeaf

// Inside `configure(_ app: Application)`:
app.leaf.tags["localize"] = LocalizeTag()
app.leaf.tags["locale"] = LocaleTag()
app.leaf.tags["localeLinks"] = LocaleLinksTag()

Afterwards you can call them inside the Leaf templates:

<!-- String localization -->
#localize("thisisthelingokey")
#localize("lingokeywithvariable", "{\"foo\":\"bar\"}")

<!-- Get current locale -->
<html lang="#locale()">

<!-- Generate link canonical and alternate tags -->
#localeLinks("http://example.com/", "/canonical/path/")

Learn more

  • Lingo - learn more about the localization file format, pluralization support, and see how you can get the most out of the Lingo.

Download Details:

Author: Vapor-community
Source Code: https://github.com/vapor-community/Lingo-Vapor 
License: MIT license

#swift #localization #server #vapor 

Lingo-Vapor: Vapor Provider for Lingo - The Swift Localization Library
Rupert  Beatty

Rupert Beatty

1667838720

BartyCrouch: Localization/I18n

BartyCrouch

BartyCrouch incrementally updates your Strings files from your Code and from Interface Builder files. "Incrementally" means that BartyCrouch will by default keep both your already translated values and even your altered comments. Additionally you can also use BartyCrouch for machine translating from one language to 60+ other languages. Using BartyCrouch is as easy as running a few simple commands from the command line what can even be automated using a build script within your project.

Checkout this blog post to learn how you can effectively use BartyCrouch in your projects.

Requirements

  • Xcode 13.3+ & Swift 5.6+
  • Xcode Command Line Tools (see here for installation instructions)

Getting Started

Installation

Via Homebrew

To install Bartycrouch the first time, simply run the command:

brew install bartycrouch

To update to the newest version of BartyCrouch when you have an old version already installed run:

brew upgrade bartycrouch

Via Mint

To install or update to the latest version of BartyCrouch simply run this command:

mint install FlineDev/BartyCrouch

Configuration

To configure BartyCrouch for your project, first create a configuration file within your projects root directory. BartyCrouch can do this for you:

bartycrouch init

Now you should have a file named .bartycrouch.toml with the following contents:

[update]
tasks = ["interfaces", "code", "transform", "normalize"]

[update.interfaces]
paths = ["."]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
defaultToBase = false
ignoreEmptyStrings = false
unstripped = false
ignoreKeys = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"]

[update.code]
codePaths = ["."]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
localizablePaths = ["."]
defaultToKeys = false
additive = true
unstripped = false
ignoreKeys = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"]

[update.transform]
codePaths = ["."]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
localizablePaths = ["."]
transformer = "foundation"
supportedLanguageEnumPath = "."
typeName = "BartyCrouch"
translateMethodName = "translate"

[update.normalize]
paths = ["."]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
sourceLocale = "en"
harmonizeWithSource = true
sortByKeys = true

[lint]
paths = ["."]
subpathsToIgnore = [".git", "carthage", "pods", "build", ".build", "docs"]
duplicateKeys = true
emptyValues = true

This is the default configuration of BartyCrouch and should work for most projects as is. In order to use BartyCrouch to its extent, it is recommended though to consider making the following changes:

  1. To speed it up significantly, provide more specific paths for any key containing path if possible (especially in the update.transform section, e.g. ["App/Sources"] for codePaths or ["App/Supporting Files"] for supportedLanguageEnumPaths).
  2. Remove the code task if your project is Swift-only and you use the new transform update task.
  3. If you are using SwiftGen with the structured-swift4 template, you will probably want to use the transform task and change its transformer option to swiftgenStructured.
  4. If you decided to use the transform task, create a new file in your project (e.g. under SupportingFiles) named BartyCrouch.swift and copy the following code:
//  This file is required in order for the `transform` task of the translation helper tool BartyCrouch to work.
//  See here for more details: https://github.com/FlineDev/BartyCrouch

import Foundation

enum BartyCrouch {
    enum SupportedLanguage: String {
        // TODO: remove unsupported languages from the following cases list & add any missing languages
        case arabic = "ar"
        case chineseSimplified = "zh-Hans"
        case chineseTraditional = "zh-Hant"
        case english = "en"
        case french = "fr"
        case german = "de"
        case hindi = "hi"
        case italian = "it"
        case japanese = "ja"
        case korean = "ko"
        case malay = "ms"
        case portuguese = "pt-BR"
        case russian = "ru"
        case spanish = "es"
        case turkish = "tr"
    }

    static func translate(key: String, translations: [SupportedLanguage: String], comment: String? = nil) -> String {
        let typeName = String(describing: BartyCrouch.self)
        let methodName = #function

        print(
            "Warning: [BartyCrouch]",
            "Untransformed \(typeName).\(methodName) method call found with key '\(key)' and base translations '\(translations)'.",
            "Please ensure that BartyCrouch is installed and configured correctly."
        )

        // fall back in case something goes wrong with BartyCrouch transformation
        return "BC: TRANSFORMATION FAILED!"
    }
}
  1. If you don't develop in English as the first localized language, you should update the sourceLocale of the normalize task.
  2. If you want to use the machine translation feature of BartyCrouch, add translate to the tasks list at the top and copy the following section into the configuration file with secret replaced by your Microsoft Translator Text API Subscription Key:
[update.translate]
paths = "."
translator = "microsoftTranslator"
secret = "<#Subscription Key#>"
sourceLocale = "en"

Usage

Before using BartyCrouch please make sure you have committed your code. Also, we highly recommend using the build script method described below.


bartycrouch accepts one of the following sub commands:

  • update: Updates your .strings file contents according to your configuration.
  • lint: Checks your .strings file contents for empty values & duplicate keys.

Also the following command line options can be provided:

  • -v, --verbose: Prints more detailed information about the executed command.
  • -x, --xcode-output: Prints warnings & errors in Xcode compatible format.
  • -w, --fail-on-warnings: Returns a failed status code if any warning is encountered.
  • -p, --path: Specifies a different path than current to run BartyCrouch from there.

update subcommand

The update subcommand can be run with one or multiple of the following tasks:

  • interfaces: Updates .strings files of Storyboards & XIBs.
  • code: Updates Localizable.strings file from NSLocalizedString entries in code.
  • transform: A mode where BartyCrouch replaces a specific method call to provide translations in multiple languages in a single line. Only supports Swift files.
  • translate: Updates missing translations in other languages than the source language.
  • normalize: Sorts & cleans up .strings files.

In order to configure which tasks are executed, edit this section in the config file:

[update]
tasks = ["interfaces", "code", "transform", "normalize"]

Options for interfaces

  • paths: The directory / directories to search for Storyboards & XIB files.
  • subpathsToIgnore: The subpaths to be ignored inside the directories found via the paths option.
  • defaultToBase: Add Base translation as value to new keys.
  • ignoreEmptyStrings: Doesn't add views with empty values.
  • unstripped: Keeps whitespaces at beginning & end of Strings files.
  • ignoreKeys: Keys (e.g. in the comment) indicating that specific translation entries should be ignored when generating String files. Useful to ignore strings that are gonna be translated in code.

Options for code

  • codePaths: The directory / directories to search for Swift code files.
  • subpathsToIgnore: The subpaths to be ignored inside the directories found via the paths option.
  • localizablePaths: The enclosing path(s) containing the localized Localizable.strings files.
  • defaultToKeys: Add new keys both as key and value.
  • additive: Prevents cleaning up keys not found in code.
  • customFunction: Use alternative name to search for strings to localize, in addition to NSLocalizedString, and CFCopyLocalizedString. Defaults to LocalizedStringResource.
  • customLocalizableName: Use alternative name for Localizable.strings.
  • unstripped: Keeps whitespaces at beginning & end of Strings files.
  • plistArguments: Use a plist file to store all the code files for the ExtractLocStrings tool. (Recommended for large projects.)
  • ignoreKeys: Keys (e.g. in the comment) indicating that specific translation entries should be ignored when generating String files.
  • overrideComments: Always overrides the comment with the keys new translation, useful for IB files.

Options for transform

  • codePaths: The directory / directories to search for Swift code files.
  • subpathsToIgnore: The subpaths to be ignored inside the directories found via the paths option.
  • localizablePaths: The enclosing path(s) containing the localized Localizable.strings files.
  • transformer: Specifies the replacement code. Use foundation for NSLocalizedString or swiftgenStructured for L10n entries.
  • supportedLanguageEnumPath: The enclosing path containing the SupportedLanguage enum.
  • typeName: The name of the type enclosing the SupportedLanguage enum and translate method.
  • translateMethodName: The name of the translate method to be replaced.
  • customLocalizableName: Use alternative name for Localizable.strings.
  • separateWithEmptyLine: Set to false if you don't want to have empty lines between Strings entries. Defaults to `true.

Options for translate

  • paths: The directory / directories to search for Strings files.
  • subpathsToIgnore: The subpaths to be ignored inside the directories found via the paths option.
  • translator: Specifies the translation API. Use microsoftTranslator or deepL.
  • secret: Your Microsoft Translator Text API Subscription Key or Authentication Key for DeepL API.
  • sourceLocale: The source language to translate from.
  • separateWithEmptyLine: Set to false if you don't want to have empty lines between Strings entries. Defaults to `true.

Options for normalize

  • paths: The directory / directories to search for Strings files.
  • subpathsToIgnore: The subpaths to be ignored inside the directories found via the paths option.
  • sourceLocale: The source language to harmonize keys of other languages with.
  • harmonizeWithSource: Synchronizes keys with source language.
  • sortByKeys: Alphabetically sorts translations by their keys.
  • separateWithEmptyLine: Set to false if you don't want to have empty lines between Strings entries. Defaults to `true.

lint subcommand

The lint subcommand was designed to analyze a project for typical translation issues. The current checks include:

  • duplicateKeys: Finds duplicate keys within the same file.
  • emptyValues: Finds empty values for any language.

Note that the lint command can be used both on CI and within Xcode via the build script method:

  • In Xcode the -x or --xcode-output command line argument should be used to get warnings which point you directly to the found issue.
  • When running on the CI you should specify the -w or --fail-on-warnings argument to make sure BartyCrouch fails if any warnings are encountered.

Localization Workflow via transform

When the transform update task is configured (see recommended step 4 in the Configuration section above) and you are using the build script method, you can use the following simplified process for writing localized code during development:

  1. Instead of NSLocalizedString calls you can use BartyCrouch.translate and specify a key, translations (if any) and optionally a comment. For example:
self.title = BartyCrouch.translate(key: "onboarding.first-page.header-title",  translations: [.english: "Welcome!"])
  1. Once you build your app, BartyCrouch will automatically add the new translation key to all your Localizable.strings files and add the provided translations as values for the provided languages.
  2. Additionally, during the same build BartyCrouch will automatically replace the above call to BartyCrouch.translate with the proper translation call, depending on your transformer option setting.

The resulting code depends on your transformer option setting:

When set to foundation, the above code will transform to:

self.title = NSLocalizedString("onboarding.first-page.header-title", comment: "")

When set to swiftgenStructured it will transform to:

self.title = L10n.Onboarding.FirstPage.headerTitle

Advantages of transform over the code task:

  • You can provide translations for keys without switching to the Strings files.
  • In case you use SwiftGen, you don't need to replace calls to NSLocalizedString with L10n calls manually after running BartyCrouch.
  • Can be combined with the machine translation feature to provide a source language translation in code and let BartyCrouch translate it to all supported languages in a single line & without ever leaving the code.

Disadvantages of transform over the code task:

  • Only works for Swift Code. No support for Objective-C. (You can use both methods simultaneously though.)
  • Xcode will mark the freshly transformed code as errors (but build will succeed anyways) until next build.
  • Not as fast as code since SwiftSyntax currently isn't particularly fast. (But this should improve over time!)

NOTE: As of version 4.x of BartyCrouch formatted localized Strings are not supported by this automatic feature.

Localizing strings of LocalizableStringResource type (AppIntents, ...)

Historically, Apple platforms used CFCopyLocalizedString, and NSLocalizedString macros and their variants, to mark strings used in code to be localized, and to load their localized versions during runtime from Localizable.strings file.

Since introduction of the AppIntents framework, the localized strings in code can also be typed as LocalizedStringResource, and are no longer marked explicitly.

Let's examine this snippet of AppIntents code:

struct ExportAllTransactionsIntent: AppIntent {
    static var title: LocalizedStringResource = "Export all transactions"
    
    static var description =
        IntentDescription("Exports your transaction history as CSV data.")
}

In the example above, both the "Export all transactions", and "Exports your transaction history as CSV data." are actually StaticString instances that will be converted during compilation into LocalizedStringResource instances, and will lookup their respective localizations during runtime from Localized.strings file the same way as when using NSLocalizedString in the past. The only exception being that such strings are not marked explicitly, and require swift compiler to parse and extract such strings for localization. This is what Xcode does from version 13 when using Product -> Export Localizations... option.

In order to continue translating these strings with bartycrouch it is required to mark them explicitely with LocalizedStringResource(_: String, comment: String) call, and specify customFunction="LocalizedStringResource" in code task options.

The example AppIntents code that can be localized with bartycrouch will look like this:

struct ExportAllTransactionsIntent: AppIntent {
    static var title = LocalizedStringResource("Export all transactions", comment: "")
    
    static var description =
        IntentDescription(LocalizedStringResource("Exports your transaction history as CSV data.", comment: ""))
}

Note that you must use the full form of LocalizedStringResource(_: StaticString, comment: StaticString) for the bartycrouch, or more specifically for the extractLocStrings (see xcrun extractLocStrings) to properly parse the strings.

Build Script

In order to truly profit from BartyCrouch's ability to update & lint your .strings files you can make it a natural part of your development workflow within Xcode. In order to do this select your target, choose the Build Phases tab and click the + button on the top left corner of that pane. Select New Run Script Phase and copy the following into the text box below the Shell: /bin/sh of your new run script phase:

export PATH="$PATH:/opt/homebrew/bin"

if which bartycrouch > /dev/null; then
    bartycrouch update -x
    bartycrouch lint -x
else
    echo "warning: BartyCrouch not installed, download it from https://github.com/FlineDev/BartyCrouch"
fi

Next, make sure the BartyCrouch script runs before the steps Compiling Sources (and SwiftGen if used) by moving it per drag & drop, for example right after Target Dependencies.

Now BartyCrouch will be run on each build and you won't need to call it manually ever (again). Additionally, all your co-workers who don't have BartyCrouch installed will see a warning with a hint on how to install it.

Note: Please make sure you commit your code using source control regularly when using the build script method.


Exclude specific Views / NSLocalizedStrings from Localization

Sometimes you may want to ignore some specific views containing localizable texts e.g. because their values are going to be set programmatically.

For these cases you can simply include #bartycrouch-ignore! or the shorthand #bc-ignore! into your value within your base localized Storyboard/XIB file. Alternatively you can add #bc-ignore! into the field "Comment For Localizer" box in the utilities pane.

This will tell BartyCrouch to ignore this specific view when updating your .strings files.

Here's an example of how a base localized view in a XIB file with partly ignored strings might look like:

Here's an example with the alternative comment variant:

You can also use #bc-ignore! in your NSLocalizedString macros comment part to ignore them so they are not added to your Localizable.strings. This might be helpful when you are using a .stringsdict file to handle pluralization (see docs).

For example you can do something like this:

func updateTimeLabel(minutes: Int) {
  String.localizedStringWithFormat(NSLocalizedString("%d minute(s) ago", comment: "pluralized and localized minutes #bc-ignore!"), minutes)
}

The %d minute(s) ago key will be taken from Localizable.stringsdict file, not from Localizable.strings, that's why it should be ignored by BartyCrouch.

Donation

BartyCrouch was brought to you by Cihat Gündüz in his free time. If you want to thank me and support the development of this project, please make a small donation on PayPal. In case you also like my other open source contributions and articles, please consider motivating me by becoming a sponsor on GitHub or a patron on Patreon.

Thank you very much for any donation, it really helps out a lot! 💯

Migration Guides

See the file MIGRATION_GUIDES.md.

Contributing

Contributions are welcome. Feel free to open an issue on GitHub with your ideas or implement an idea yourself and post a pull request. If you want to contribute code, please try to follow the same syntax and semantic in your commit messages (see rationale here). Also, please make sure to add an entry to the CHANGELOG.md file which explains your change.

In order for the tests to run build issues, you need to run – also add an an API key in the new file to run the translations tests, too:

cp Tests/BartyCrouchTranslatorTests/Secrets/secrets.json.sample Tests/BartyCrouchTranslatorTests/Secrets/secrets.json

After Release Checklist:

  1. Run make portable_zip to generate .build/release/portable_bartycrouch.zip
  2. Create new release with text from new CHANGELOG.md section & attach portable_bartycrouch.zip as binary
  3. Run pod trunk push to make a new release known to CocoaPods
  4. Update tag and revision in Formula/bartycrouch.rb, commit & push change
  5. Run brew bump-formula-pr bartycrouch --tag=<tag> --revision=<revision>

Important Notice

There's now a new Mac app called ReMafoX which is the successor to BartyCrouch. It improves upon several aspects of BartyCrouch, such as having no flaky dependencies, adding pluralization support, smart machine translation, a built-in SwiftUI-compatible enum generator, built-in step-by-step instructions for easier setup, detailed explanations of all config options, and even a set of video guides for things like setup, key naming best practices and team onboarding. Get it for free here.

Note that ReMafoX is being actively worked on, you can even vote for or request new features here. In comparison, BartyCrouch is kept up-to-date mostly by the community.


Download Details:

Author: FlineDev
Source Code: https://github.com/FlineDev/BartyCrouch 
License: MIT license

#swift #language #translation #localization #xcode 

BartyCrouch: Localization/I18n

A Simple Localization Library Which Aims to Have Multiple Localization

zooper_flutter_localization

A simple localization library which aims to have multiple localization files. This lib is aimed to work with get_it or injectable, but it should works for any other framework.

Example

This example aims to use this package with injectable, but you should be able to implement it with any other framework, or even without

Importing

Add this line to your pubspec.yaml:

zooper_flutter_localization: <latest>

and inside your dart class:

import 'package:zooper_flutter_localization/zooper_flutter_localization.dart';

Registering with injectable

If you want to register a localizer for a specific View (better use ViewModels if you use the MVVM Pattern), you can define a Module with injectable:

import 'package:injectable/injectable.dart';
import 'package:zooper_flutter_localization/zooper_flutter_localization.dart';

@module
abstract class LocalizationModule {
  LocalizationService get localizationService;

  // Register a named localization
  @preResolve
  @Named('Titles')
  Future<ZooperLocalizer> titlesLocalizations(LocalizationService localizationService) =>
      localizationService.loadAsync('assets/localizations/titles.csv');

  // Register an other named localization
  @preResolve
  @Named('Errors')
  Future<ZooperLocalizer> errorsLocalizations(LocalizationService localizationService) =>
      localizationService.loadAsync('assets/localizations/errors.csv');

  // Register an unnamed localization but with an explicit type
  @preResolve
  Future<ZooperLocalizer<MainViewModel>> viewModelLocalizations(LocalizationService localizationService) =>
      localizationService.loadAsync('assets/localizations/errors.csv');
}

This registers a ZooperLocalizer and loads the translations from the defined file. Then you can inject it easily:

class TestView {
  final ZooperLocalizer<TestViewModel> _localizer;

  TestView(this._localizer);
}

or

class TestView {
  final ZooperLocalizer _localizer;

  TestView(@Named('YourName') this._localizer);
}

Accessing a translation

This can be done in multiple ways. Easist is this:

_localizer['Hello'];

but you can do also:

_localizer.getLocalization('Hello');

or with a specific locale:

_localizer.getLocalizationByLocale('Hello', Locale('de', 'DE'))

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add zooper_flutter_localization

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  zooper_flutter_localization: ^1.0.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:zooper_flutter_localization/zooper_flutter_localization.dart'; 

Download Details:

Author: 

Source Code: https://pub.dev/packages/zooper_flutter_localization

#flutter #localization 

A Simple Localization Library Which Aims to Have Multiple Localization

Laravel-localization: Easy Localization for Laravel

Laravel Localization

Easy i18n localization for Laravel, an useful tool to combine with Laravel localization classes.

The package offers the following:

  • Detect language from browser
  • Smart redirects (Save locale in session/cookie)
  • Smart routing (Define your routes only once, no matter how many languages you use)
  • Translatable Routes
  • Supports caching & testing
  • Option to hide default locale in url
  • Many snippets and helpers (like language selector)

Laravel compatibility

Laravellaravel-localization
4.0.x0.13.x
4.1.x0.13.x
4.2.x0.15.x
5.0.x/5.1.x1.0.x
5.2.x-5.4.x (PHP 7 not required)1.2.x
5.2.x-5.8.x (PHP 7 required)1.3.x
5.2.0-6.x (PHP 7 required)1.4.x
5.2.0-9.x (PHP 7 required)1.7.x

Installation

Install the package via composer: composer require mcamara/laravel-localization

For Laravel 5.4 and below it necessary to register the service provider.

Config Files

In order to edit the default configuration you may execute:

php artisan vendor:publish --provider="Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider"

After that, config/laravellocalization.php will be created.

The configuration options are:

  • supportedLocales Languages of your app (Default: English & Spanish).
  • useAcceptLanguageHeader If true, then automatically detect language from browser.
  • hideDefaultLocaleInURL If true, then do not show default locale in url.
  • localesOrder Sort languages in custom order.
  • localesMapping Rename url locales.
  • utf8suffix Allow changing utf8suffix for CentOS etc.
  • urlsIgnored Ignore specific urls.

Register Middleware

You may register the package middleware in the app/Http/Kernel.php file:

<?php namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel {
    /**
    * The application's route middleware.
    *
    * @var array
    */
    protected $routeMiddleware = [
        /**** OTHER MIDDLEWARE ****/
        'localize'                => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
        'localizationRedirect'    => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
        'localeSessionRedirect'   => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
        'localeCookieRedirect'    => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
        'localeViewPath'          => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class
    ];
}

Usage

Add the following to your routes file:

// routes/web.php

Route::group(['prefix' => LaravelLocalization::setLocale()], function()
{
    /** ADD ALL LOCALIZED ROUTES INSIDE THIS GROUP **/
    Route::get('/', function()
    {
        return View::make('hello');
    });

    Route::get('test',function(){
        return View::make('test');
    });
});

/** OTHER PAGES THAT SHOULD NOT BE LOCALIZED **/

Once this route group is added to the routes file, a user can access all locales added into supportedLocales (en and es by default). For example, the above route file creates the following addresses:

// Set application language to English
http://url-to-laravel/en
http://url-to-laravel/en/test

// Set application language to Spanish
http://url-to-laravel/es
http://url-to-laravel/es/test

// Set application language to English or Spanish (depending on browsers default locales)
// if nothing found set to default locale
http://url-to-laravel
http://url-to-laravel/test

The package sets your application locale App::getLocale() according to your url. The locale may then be used for Laravel's localization features.

You may add middleware to your group like this:

Route::group(
[
    'prefix' => LaravelLocalization::setLocale(),
    'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]
], function(){ //...
});

Recommendations

1.: It is strongly recommended to use a redirecting middleware. Urls without locale should only be used to determine browser/default locale and to redirect to the localized url. Otherwise, when search engine robots crawl for example http://url-to-laravel/test they may get different language content for each visit. Also having multiple urls for the same content creates a SEO duplicate-content issue.

2.: It is strongly recommended to localize your links, even if you use a redirect middleware. Otherwise, you will cause at least one redirect each time a user clicks on a link. Also, any action url from a post form must be localized, to prevent that it gets redirected to a get request.

Redirect Middleware

The following redirection middleware depends on the settings of hideDefaultLocaleInURL and useAcceptLanguageHeader in config/laravellocalization.php:

LocaleSessionRedirect

Whenever a locale is present in the url, it will be stored in the session by this middleware.

If there is no locale present in the url, then this middleware will check the following

  • If no locale is saved in session and useAcceptLanguageHeader is set to true, compute locale from browser and redirect to url with locale.
  • If a locale is saved in session redirect to url with locale, unless its the default locale and hideDefaultLocaleInURL is set to true.

For example, if a user navigates to http://url-to-laravel/test and en is the current locale, it would redirect him automatically to http://url-to-laravel/en/test.

LocaleCookieRedirect

Similar to LocaleSessionRedirect, but it stores value in a cookie instead of a session.

Whenever a locale is present in the url, it will be stored in the cookie by this middleware.

In there is no locale present in the url, then this middleware will check the following

  • If no locale is saved in cookie and useAcceptLanguageHeader is set to true, compute locale from browser and redirect to url with locale.
  • If a locale is saved in cookie redirect to url with locale, unless its the default locale and hideDefaultLocaleInURL is set to true.

For example, if a user navigates to http://url-to-laravel/test and de is the current locale, it would redirect him automatically to http://url-to-laravel/de/test.

LaravelLocalizationRedirectFilter

When the default locale is present in the url and hideDefaultLocaleInURL is set to true, then the middleware redirects to the url without locale.

For example, if es is the default locale, then http://url-to-laravel/es/test would be redirected to http://url-to-laravel/test and theApp::getLocale() would be set to es.

Helpers

This package comes with a bunch of helpers.

Localized URLs

Localized URLS taken into account route model binding when generating the localized route, aswell as the hideDefaultLocaleInURL and Translated Routes settings.

Get localized URL

    // If current locale is Spanish, it returns `/es/test`
    <a href="{{ LaravelLocalization::localizeUrl('/test') }}">@lang('Follow this link')</a>

Get localized URL for an specific locale

Get current URL in specific locale:

// Returns current url with English locale.
{{ LaravelLocalization::getLocalizedURL('en') }}

Get Clean routes

Returns a URL clean of any localization.

// Returns /about
{{ LaravelLocalization::getNonLocalizedURL('/es/about') }}

Get URL for an specific translation key

Returns a route, localized to the desired locale. If the translation key does not exist in the locale given, this function will return false.

// Returns /es/acerca
{{ LaravelLocalization::getURLFromRouteNameTranslated('es', 'routes.about') }}

Get Supported Locales

Return all supported locales and their properties as an array.

{{ LaravelLocalization::getSupportedLocales() }}

Get Supported Locales Custom Order

Return all supported locales but in the order specified in the configuration file. You can use this function to print locales in the language selector.

{{ LaravelLocalization::getLocalesOrder() }}

Get Supported Locales Keys

Return an array with all the keys for the supported locales.

{{ LaravelLocalization::getSupportedLanguagesKeys() }}

Get Current Locale

Return the key of the current locale.

{{ LaravelLocalization::getCurrentLocale() }}

Get Current Locale Name

Return current locale's name as string (English/Spanish/Arabic/ ..etc).

{{ LaravelLocalization::getCurrentLocaleName() }}

Get Current Locale Native Name

Return current locale's native name as string (English/Español/عربى/ ..etc).

{{ LaravelLocalization::getCurrentLocaleNative() }}

Get Current Locale Direction

Return current locale's direction as string (ltr/rtl).

{{ LaravelLocalization::getCurrentLocaleDirection() }}

Get Current Locale Script

Return the ISO 15924 code for the current locale script as a string; "Latn", "Cyrl", "Arab", etc.

{{ LaravelLocalization::getCurrentLocaleScript() }}

Set view-base-path to current locale

Register the middleware LaravelLocalizationViewPath to set current locale as view-base-path.

Now you can wrap your views in language-based folders like the translation files.

resources/views/en/, resources/views/fr, ...

Map your own custom lang url segments

As you can modify the supportedLocales even by renaming their keys, it is possible to use the string uk instead of en-GB to provide custom lang url segments. Of course, you need to prevent any collisions with already existing keys and should stick to the convention as long as possible. But if you are using such a custom key, you have to store your mapping to the localesMapping array. This localesMapping is needed to enable the LanguageNegotiator to correctly assign the desired locales based on HTTP Accept Language Header. Here is a quick example how to map HTTP Accept Language Header 'en-GB' to url segment 'uk':

// config/laravellocalization.php

'localesMapping' => [
    'en-GB' => 'uk'
],

After that http://url-to-laravel/en-GB/a/b/c becomes http://url-to-laravel/uk/a/b/c.

LaravelLocalization::getLocalizedURL('en-GB', 'a/b/c'); // http://url-to-laravel/uk/a/b/c
LaravelLocalization::getLocalizedURL('uk', 'a/b/c'); // http://url-to-laravel/uk/a/b/c

Creating a language selector

If you're supporting multiple locales in your project you will probably want to provide the users with a way to change language. Below is a simple example of blade template code you can use to create your own language selector.

<ul>
    @foreach(LaravelLocalization::getSupportedLocales() as $localeCode => $properties)
        <li>
            <a rel="alternate" hreflang="{{ $localeCode }}" href="{{ LaravelLocalization::getLocalizedURL($localeCode, null, [], true) }}">
                {{ $properties['native'] }}
            </a>
        </li>
    @endforeach
</ul>

Here default language will be forced in getLocalizedURL() to be present in the URL even hideDefaultLocaleInURL = true.

Note that Route Model Binding is supported.

Translated Routes

You may translate your routes. For example, http://url/en/about and http://url/es/acerca (acerca is about in spanish) or http://url/en/article/important-article and http://url/es/articulo/important-article (article is articulo in spanish) would be redirected to the same controller/view as follows:

It is necessary that at least the localize middleware in loaded in your Route::group middleware (See installation instruction).

For each language, add a routes.php into resources/lang/**/routes.php folder. The file contains an array with all translatable routes. For example, like this:

<?php
// resources/lang/en/routes.php
return [
    "about"    =>  "about",
    "article"  =>  "article/{article}",
];
<?php
// resources/lang/es/routes.php
return [
    "about"    =>  "acerca",
    "article"  =>  "articulo/{article}",
];

You may add the routes in routes/web.php like this:

Route::group(['prefix' => LaravelLocalization::setLocale(),
              'middleware' => [ 'localize' ]], function () {

    Route::get(LaravelLocalization::transRoute('routes.about'), function () {
        return view('about');
    });

    Route::get(LaravelLocalization::transRoute('routes.article'), function (\App\Article $article) {
        return $article;
    });

    //,...
});

Once files are saved, you can access http://url/en/about , http://url/es/acerca , http://url/en/article/important-article and http://url/es/articulo/important-article without any problem.

Translatable route parameters

Maybe you noticed in the previous example the English slug in the Spanish url:

http://url/es/articulo/important-article

It is possible to have translated slugs, for example like this:

http://url/en/article/important-change
http://url/es/articulo/cambio-importante

However, in order to do this, each article must have many slugs (one for each locale). Its up to you how you want to implement this relation. The only requirement for translatable route parameters is, that the relevant model implements the interface LocalizedUrlRoutable.

Implementing LocalizedUrlRoutable

To implement \Mcamara\LaravelLocalization\Interfaces\LocalizedUrlRoutable, one has to create the function getLocalizedRouteKey($locale), which must return for a given locale the translated slug. In the above example, inside the model article, getLocalizedRouteKey('en') should return important-change and getLocalizedRouteKey('es') should return cambio-importante.

Route Model Binding

To use route-model-binding, one should overwrite the function resolveRouteBinding($slug) in the model. The function should return the model that belongs to the translated slug $slug. For example:

public function resolveRouteBinding($slug)
{
        return static::findByLocalizedSlug($slug)->first() ?? abort(404);
}

Tutorial Video

You may want to checkout this video which demonstrates how one may set up translatable route parameters.

Events

You can capture the URL parameters during translation if you wish to translate them too. To do so, just create an event listener for the routes.translation event like so:

Event::listen('routes.translation', function($locale, $attributes)
{
    // Do your magic

    return $attributes;
});

Be sure to pass the locale and the attributes as parameters to the closure. You may also use Event Subscribers, see: http://laravel.com/docs/events#event-subscribers

Caching routes

To cache your routes, use:

php artisan route:trans:cache

... instead of the normal route:cache command. Using artisan route:cache will not work correctly!

For the route caching solution to work, it is required to make a minor adjustment to your application route provision.

In your App's RouteServiceProvider, use the LoadsTranslatedCachedRoutes trait:

<?php
class RouteServiceProvider extends ServiceProvider
{
    use \Mcamara\LaravelLocalization\Traits\LoadsTranslatedCachedRoutes;

For more details see here.

Common Issues

POST is not working

This may happen if you do not localize your action route that is inside your Routes::group. This may cause a redirect, which then changes the post request into a get request. To prevent that, simply use the localize helper.

For example, if you use Auth::routes() and put them into your Route::group Then

<form action="/logout" method="POST">
<button>Logout</button>
</form>

will not work. Instead, one has to use

<form action="{{  \LaravelLocalization::localizeURL('/logout') }} " method="POST">
<button>Logout</button>
</form>

Another way to solve this is to put http method to config to 'laravellocalization.httpMethodsIgnored' to prevent of processing this type of requests

MethodNotAllowedHttpException

If you do not localize your post url and use a redirect middleware, then the post request gets redirected as a get request. If you have not defined such a get route, you will cause this exception.

To localize your post url see the example in POST is not working.

Validation message is only in default locale

This also happens if you did not localize your post url. If you don't localize your post url, the default locale is set while validating, and when returning to back() it shows the validation message in default locale.

To localize your post url see the example in POST is not working.

Testing

During the test setup, the called route is not yet known. This means no language can be set. When a request is made during a test, this results in a 404 - without the prefix set the localized route does not seem to exist.

To fix this, you can use this function to manually set the language prefix:

// TestCase.php
protected function refreshApplicationWithLocale($locale)
{
    self::tearDown();
    putenv(LaravelLocalization::ENV_ROUTE_KEY . '=' . $locale);
    self::setUp();
}

protected function tearDown()
{
    putenv(LaravelLocalization::ENV_ROUTE_KEY);
    parent::tearDown();
}

// YourTest.php
public function testBasicTest()
{
    $this->refreshApplicationWithLocale('en');
    // Testing code
}

Collaborators

Ask mcamara if you want to be one of them!

Changelog

View changelog here -> changelog

Download Details:

Author: mcamara
Source Code: https://github.com/mcamara/laravel-localization 
License: MIT license

#php #laravel #localization 

 Laravel-localization: Easy Localization for Laravel
Rupert  Beatty

Rupert Beatty

1665877800

SwiftGen: The Swift Code Generator for Your Assets, Storyboards

SwiftGen

SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them type-safe to use.

There are multiple benefits in using this:

  • Avoid any risk of typo when using a String
  • Free auto-completion
  • Avoid the risk of using a non-existing asset name
  • All this will be ensured by the compiler and thus avoid the risk of crashing at runtime.

Also, it's fully customizable thanks to Stencil templates, so even if it comes with predefined templates, you can make your own to generate whatever code fits your needs and your guidelines!

Installation

There are multiple possibilities to install SwiftGen on your machine or in your project, depending on your preferences and needs:

Download the ZIP for the latest release

We recommend that you unarchive the ZIP inside your project directory and commit its content to git. This way, all coworkers will use the same version of SwiftGen for this project.

If you unarchived the ZIP file in a folder e.g. called swiftgen at the root of your project directory, you can then invoke SwiftGen in your Script Build Phase using:

"${PROJECT_DIR}/swiftgen/bin/swiftgen" …

Via CocoaPods

If you're using CocoaPods, simply add pod 'SwiftGen', '~> 6.0' to your Podfile.

Then execute pod install --repo-update (or pod update SwiftGen if you want to update an existing SwiftGen installation) to download and install the SwiftGen binaries and dependencies in Pods/SwiftGen/bin/swiftgen next to your project.

Given that you can specify an exact version for SwiftGen in your Podfile, this allows you to ensure all coworkers will use the same version of SwiftGen for this project.

You can then invoke SwiftGen in your Script Build Phase using:

if [[ -f "${PODS_ROOT}/SwiftGen/bin/swiftgen" ]]; then
  "${PODS_ROOT}/SwiftGen/bin/swiftgen" …
else
  echo "warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it."
fi

Similarly, be sure to use Pods/SwiftGen/bin/swiftgen instead of just swiftgen where we mention commands with swiftgen in the rest of the documentation.

Note: SwiftGen isn't really a pod, as it's not a library your code will depend on at runtime; so the installation via CocoaPods is just a trick that installs the SwiftGen binaries in the Pods/ folder, but you won't see any swift files in the Pods/SwiftGen group in your Xcode's Pods.xcodeproj. That's normal; the SwiftGen binary is still present in that folder in the Finder.


Via Homebrew (system-wide installation)

To install SwiftGen via Homebrew, simply use:

$ brew update
$ brew install swiftgen

This will install SwiftGen system-wide. The same version of SwiftGen will be used for all projects on that machine, and you should make sure all your coworkers have the same version of SwiftGen installed on their machine too.

You can then invoke swiftgen directly in your Script Build Phase (as it will be in your $PATH already):

swiftgen … 

Via Mint (system-wide installation)

❗️SwiftGen 6.0 or higher only.

To install SwiftGen via Mint, simply use:

$ mint install SwiftGen/SwiftGen

Compile from source (only recommended if you need features from the `stable` branch or want to test a PR)

This solution is when you want to build and install the latest version from stable and have access to features which might not have been released yet.

  • If you have homebrew installed, you can use the following command to build and install the latest commit:
brew install swiftgen --HEAD
  • Alternatively, you can clone the repository and use rake cli:install to build the tool and install it from any branch, which could be useful to test SwiftGen in a fork or a Pull Request branch.

Some Ruby tools are used in the build process, and the system Ruby works well if you are running a recent macOS. However, if you are using rbenv you can run rbenv install to make sure you have a matching version of Ruby installed.

Then install the Ruby Gems:

# Install bundle if it isn't installed
gem install bundle
# Install the Ruby gems from Gemfile
bundle install

You can now install to the default locations (no parameter) or to custom locations:

# Binary is installed in `./.build/swiftgen/bin`
$ rake cli:install
# - OR -
# Binary will be installed in `~/swiftgen/bin``
$ rake cli:install[~/swiftgen/bin]

You can then invoke SwiftGen using the path to the binary where you installed it:

~/swiftgen/bin/swiftgen …

Or add the path to the bin folder to your $PATH and invoke swiftgen directly.


Known Installation Issues On macOS Before 10.14.4

Starting with SwiftGen 6.2.1, if you get an error similar to dyld: Symbol not found: _$s11SubSequenceSlTl when running SwiftGen, you'll need to install the Swift 5 Runtime Support for Command Line Tools.

Alternatively, you can:

  • Update to macOS 10.14.4 or later
  • Install Xcode 10.2 or later at /Applications/Xcode.app
  • Rebuild SwiftGen from source using Xcode 10.2 or later

Configuration File

❗️ If you're migrating from older SwiftGen versions, don't forget to read the Migration Guide.

SwiftGen is provided as a single command-line tool which uses a configuration file to define the various parsers to run (depending on the type of input files you need to parse) and their parameters.

To create a sample configuration file as a starting point to adapt to your needs, run swiftgen config init.

Each parser described in the configuration file (strings, fonts, ib, …) typically corresponds to a type of input resources to parse (strings files, IB files, Font files, JSON files, …), allowing you to generate constants for each types of those input files.

To use SwiftGen, simply create a swiftgen.yml YAML file (either manually or using swiftgen config init) then edit it to adapt to your project. The config file should list all the parsers to invoke, and for each parser, the list of inputs/outputs/templates/parameters to use for it.

For example:

strings:
  inputs: Resources/Base.lproj
  outputs:
    - templateName: structured-swift5
      output: Generated/Strings.swift
xcassets:
  inputs:
    - Resources/Images.xcassets
    - Resources/MoreImages.xcassets
    - Resources/Colors.xcassets
  outputs:
    - templateName: swift5
      output: Generated/Assets.swift

Then you just have to invoke swiftgen config run, or even just swiftgen for short, and it will execute what's described in the configuration file.

The dedicated documentation explains the syntax and possibilities in details – like how to pass custom parameters to your templates, use swiftgen config lint to validate your config file, how to use alternate config files, and other tips.

There are also additional subcommands you can invoke from the command line to manage and configure SwiftGen:

  • The swiftgen config subcommand to help you with the configuration file, especially swiftgen config init to create a starting point for your config and swiftgen config lint to validate that your Config file is valid and has no errors
  • The swiftgen template subcommands to help you print, duplicate, find and manage templates bundled with SwiftGen

Lastly, you can use --help on swiftgen or one of its subcommand to see the detailed usage.

Directly invoking a parser without a config file

While we highly recommend the use a configuration file for performance reasons (especially if you have multiple outputs, but also because it's more flexible), it's also possible to directly invoke the available parsers individually using swiftgen run:

  • swiftgen run colors [OPTIONS] DIRORFILE1 …
  • swiftgen run coredata [OPTIONS] DIRORFILE1 …
  • swiftgen run files [OPTIONS] DIRORFILE1 …
  • swiftgen run fonts [OPTIONS] DIRORFILE1 …
  • swiftgen run ib [OPTIONS] DIRORFILE1 …
  • swiftgen run json [OPTIONS] DIRORFILE1 …
  • swiftgen run plist [OPTIONS] DIRORFILE1 …
  • swiftgen run strings [OPTIONS] DIRORFILE1 …
  • swiftgen run xcassets [OPTIONS] DIRORFILE1 …
  • swiftgen run yaml [OPTIONS] DIRORFILE1 …

One rare cases where this might be useful — as opposed to using a config file — is if you are working on a custom template and want to quickly test the specific parser you're working on at each iteration/version of your custom template, until you're happy with it.

Each parser command generally accepts the same options and syntax, and they mirror the options and parameters from the configuration file:

  • --output FILE or -o FILE: set the file where to write the generated code. If omitted, the generated code will be printed on stdout.
  • --templateName NAME or -n NAME: define the Stencil template to use (by name, see here for more info) to generate the output.
  • --templatePath PATH or -p PATH: define the Stencil template to use, using a full path.
  • Note: you should specify one and only one template when invoking SwiftGen. You have to use either -t or -p but should not use both at the same time (it wouldn't make sense anyway and you'll get an error if you try)
  • --filter REGEX or -f REGEX: the filter to apply to each input path. Filters are applied to actual (relative) paths, not just the filename. Each command has a default filter that you can override with this option.
  • Note: use .+ to match multiple characters (at least one), and don't forget to escape the dot (\.) if you want to match a literal dot like for an extension. Add $ at the end to ensure the path ends with the extension you want. Regular expressions will be case sensitive by default, and not anchored to the start/end of a path. For example, use .+\.xib$ to match files with a .xib extension. Use a tool such as RegExr to ensure you're using a valid regular expression.
  • Each command supports multiple input files (or directories where applicable).
  • You can always use the --help flag to see what options a command accept, e.g. swiftgen run xcassets --help.

Choosing your template

SwiftGen is based on templates (it uses Stencil as its template engine). This means that you can choose the template that fits the Swift version you're using — and also the one that best fits your preferences — to adapt the generated code to your own conventions and Swift version.

Bundled templates vs. Custom ones

SwiftGen comes bundled with some templates for each of the parsers (colors, coredata, files, fonts, ib, json, plist, strings, xcassets, yaml), which will fit most needs; simply use the templateName output option to specify the name of the template to use. But you can also create your own templates if the bundled ones don't suit your coding conventions or needs: just store them anywhere (like in your project repository) and use the templatePath output option instead of templateName, to specify their path.

💡 You can use the swiftgen template list command to list all the available bundled templates for each parser, and use swiftgen template cat to show a template's content and duplicate it to create your own variation.

For more information about how to create your own templates, see the dedicated documentation.

Templates bundled with SwiftGen:

As explained above, you can use swiftgen template list to list all templates bundled with SwiftGen. For most SwiftGen parsers, we provide, among others:

  • A swift4 template, compatible with Swift 4
  • A swift5 template, compatible with Swift 5
  • Other variants, like flat-swift4/5 and structured-swift4/5 templates for Strings, etc.

You can find the documentation for each bundled template here in the repo, with documentation organized as one folder per SwiftGen parser, then one MarkDown file per template. You can also use swiftgen template doc to open that documentation page in your browser directly from your terminal.

Each MarkDown file documents the Swift Version it's aimed for, the use case for that template (in which cases you might favor that template over others), the available parameters to customize it on invocation (using the params: key in your config file), and some code examples.

Don't hesitate to make PRs to share your improvements suggestions on the bundled templates 😉

Additional documentation

Playground

The SwiftGen.playground available in this repository will allow you to play with the code that the tool typically generates, and see some examples of how you can take advantage of it.

This allows you to have a quick look at how typical code generated by SwiftGen looks like, and how you will then use the generated constants in your code.

Dedicated Documentation in Markdown

There is a lot of documentation in the form of Markdown files in this repository, and in the related StencilSwiftKit repository as well.

Be sure to check the "Documentation" folder of each repository.

Especially, in addition to the previously mentioned Migration Guide and Configuration File documentation, the Documentation/ folder in the SwiftGen repository also includes:

Tutorials

You can also find other help & tutorial material on the internet, like this classroom about Code Generation I gave at FrenchKit in Sept'17 — and its wiki detailing a step-by-step tutorial about installing and using SwiftGen (and Sourcery too)


Available Parsers

Asset Catalog

xcassets:
  inputs: /dir/to/search/for/imageset/assets
  outputs:
    templateName: swift5
    output: Assets.swift

This will generate an enum Asset with one static let per asset (image set, color set, data set, …) in your assets catalog, so that you can use them as constants.

Example of code generated by the bundled template

internal enum Asset {
  internal enum Files {
    internal static let data = DataAsset(value: "Data")
    internal static let readme = DataAsset(value: "README")
  }
  internal enum Food {
    internal enum Exotic {
      internal static let banana = ImageAsset(value: "Exotic/Banana")
      internal static let mango = ImageAsset(value: "Exotic/Mango")
    }
    internal static let `private` = ImageAsset(value: "private")
  }
  internal enum Styles {
    internal enum Vengo {
      internal static let primary = ColorAsset(value: "Vengo/Primary")
      internal static let tint = ColorAsset(value: "Vengo/Tint")
    }
  }
  internal enum Symbols {
    internal static let exclamationMark = SymbolAsset(name: "Exclamation Mark")
    internal static let plus = SymbolAsset(name: "Plus")
  }
  internal enum Targets {
    internal static let bottles = ARResourceGroupAsset(name: "Bottles")
    internal static let paintings = ARResourceGroupAsset(name: "Paintings")
  }
}

Usage Example

// You can create new images by referring to the enum instance and calling `.image` on it:
let bananaImage = Asset.Exotic.banana.image
let privateImage = Asset.private.image

// You can create colors by referring to the enum instance and calling `.color` on it:
let primaryColor = Asset.Styles.Vengo.primary.color
let tintColor = Asset.Styles.Vengo.tint.color

// You can create data items by referring to the enum instance and calling `.data` on it:
let data = Asset.data.data
let readme = Asset.readme.data

// You can load an AR resource group's items using:
let bottles = Asset.Targets.bottles.referenceObjects
let paintings = Asset.Targets.paintings.referenceImages

// You can create new symbol images by referring to the enum instance and calling `.image` on it (with or without configuration)
let plus = Asset.Symbols.plus.image
let style = UIImage.SymbolConfiguration(textStyle: .headline)
let styled = Asset.Symbols.exclamationMark.image(with: style)

Colors

❗️ We recommend to define your colors in your Assets Catalogs and use the xcassets parser (see above) to generate color constants, instead of using this colors parser described below.
The colors parser below is mainly useful if you support older versions of iOS where colors can't be defined in Asset Catalogs, or if you want to use Android's colors.xml files as input.

colors:
  inputs: /path/to/colors-file.txt
  outputs:
    templateName: swift5
    output: Colors.swift

This will generate a enum ColorName with one static let per color listed in the text file passed as argument.

The input file is expected to be either:

  • a plain text file, with one line per color to register, each line being composed by the Name to give to the color, followed by ":", followed by the Hex representation of the color (like rrggbb or rrggbbaa, optionally prefixed by # or 0x) or the name of another color in the file. Whitespaces are ignored.
  • a JSON file, representing a dictionary of names -> values, each value being the hex representation of the color
  • a XML file, expected to be the same format as the Android colors.xml files, containing tags <color name="AColorName">AColorHexRepresentation</color>
  • a *.clr file used by Apple's Color Palettes.

For example you can use this command to generate colors from one of your system color lists:

colors:
  inputs: ~/Library/Colors/MyColors.clr
  outputs:
    templateName: swift5
    output: Colors.swift

Generated code will look the same as if you'd use a text file.

Example of code generated by the bundled template

Given the following colors.txt file:

Cyan-Color       : 0xff66ccff
ArticleTitle     : #33fe66
ArticleBody      : 339666
ArticleFootnote  : ff66ccff
Translucent      : ffffffcc

The generated code will look like this:

internal struct ColorName {
  internal let rgbaValue: UInt32
  internal var color: Color { return Color(named: self) }

  /// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#339666"></span>
  /// Alpha: 100% <br/> (0x339666ff)
  internal static let articleBody = ColorName(rgbaValue: 0x339666ff)
  /// <span style="display:block;width:3em;height:2em;border:1px solid black;background:#ff66cc"></span>
  /// Alpha: 100% <br/> (0xff66ccff)
  internal static let articleFootnote = ColorName(rgbaValue: 0xff66ccff)

  ...
}

Usage Example

// You can create colors with the convenience constructor like this:
let title = UIColor(named: .articleBody)  // iOS
let footnote = NSColor(named: .articleFootnote) // macOS

// Or as an alternative, you can refer to enum instance and call .color on it:
let sameTitle = ColorName.articleBody.color
let sameFootnote = ColorName.articleFootnote.color

This way, no need to enter the color red, green, blue, alpha values each time and create ugly constants in the global namespace for them.

Core Data

coredata:
  inputs: /path/to/model.xcdatamodeld
  outputs:
    templateName: swift5
    output: CoreData.swift

This will parse the specified core data model(s), generate a class for each entity in your model containing all the attributes, and a few extensions if needed for relationships and predefined fetch requests.

Example of code generated by the bundled template

internal class MainEntity: NSManagedObject {
  internal class var entityName: String {
    return "MainEntity"
  }

  internal class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? {
    return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext)
  }

  @nonobjc internal class func makeFetchRequest() -> NSFetchRequest<MainEntity> {
    return NSFetchRequest<MainEntity>(entityName: entityName)
  }

  @NSManaged internal var attributedString: NSAttributedString?
  @NSManaged internal var binaryData: Data?
  @NSManaged internal var boolean: Bool
  @NSManaged internal var date: Date?
  @NSManaged internal var float: Float
  @NSManaged internal var int64: Int64
  internal var integerEnum: IntegerEnum {
    get {
      let key = "integerEnum"
      willAccessValue(forKey: key)
      defer { didAccessValue(forKey: key) }

      guard let value = primitiveValue(forKey: key) as? IntegerEnum.RawValue,
        let result = IntegerEnum(rawValue: value) else {
        fatalError("Could not convert value for key '\(key)' to type 'IntegerEnum'")
      }
      return result
    }
    set {
      let key = "integerEnum"
      willChangeValue(forKey: key)
      defer { didChangeValue(forKey: key) }

      setPrimitiveValue(newValue.rawValue, forKey: key)
    }
  }
  @NSManaged internal var manyToMany: Set<SecondaryEntity>
}

// MARK: Relationship ManyToMany

extension MainEntity {
  @objc(addManyToManyObject:)
  @NSManaged public func addToManyToMany(_ value: SecondaryEntity)

  @objc(removeManyToManyObject:)
  @NSManaged public func removeFromManyToMany(_ value: SecondaryEntity)

  @objc(addManyToMany:)
  @NSManaged public func addToManyToMany(_ values: Set<SecondaryEntity>)

  @objc(removeManyToMany:)
  @NSManaged public func removeFromManyToMany(_ values: Set<SecondaryEntity>)
}

Usage Example

// Fetch all the instances of MainEntity
let request = MainEntity.makeFetchRequest()
let mainItems = try myContext.execute(request)

// Type-safe relationships: `relatedItem` will be a `SecondaryEntity?` in this case
let relatedItem = myMainItem.manyToMany.first

Files

files:
  inputs: path/to/search
  filter: .+\.mp4$
  outputs:
    templateName: structured-swift5
    output: Files.swift

The files parser is intended to just list the name and mimetype of the files and subdirectories in a given directory. This will recursively search the specified directory using the given filter (default .*), defining a struct File for each matching file, and an hierarchical enum representing the directory structure of files.

Example of code generated by the bundled template

internal enum Files {
  /// test.txt
  internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain")
  /// subdir/
  internal enum Subdir {
    /// subdir/A Video With Spaces.mp4
    internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4")
  }
}

Usage Example

// Access files using the `url` or `path` fields
let txt = Files.testTxt.url
let video = Files.Subdir.aVideoWithSpacesMp4.path

// In addition, there are `url(locale:)` and `path(locale:)` to specify a locale
let localeTxt = Files.testTxt.url(locale: Locale.current)
let localeVideo = Files.Subdir.aVideoWithSpacesMp4.path(locale: Locale.current)

Flat Structure Support

SwiftGen also has a template if you're not interested in keeping the folder structure in the generated code.

Example of code generated by the flat bundled template

internal enum Files {
  /// test.txt
  internal static let testTxt = File(name: "test", ext: "txt", path: "", mimeType: "text/plain")
  /// subdir/A Video With Spaces.mp4
  internal static let aVideoWithSpacesMp4 = File(name: "A Video With Spaces", ext: "mp4", path: "subdir", mimeType: "video/mp4")
  }
}

Given the same file and folder structure as above the usage will now be:

// Access files using the `url` or `path` fields
let txt = Files.testTxt.url
let video = Files.aVideoWithSpacesMp4.path

// In addition, there are `url(locale:)` and `path(locale:)` to specify a locale
let localeTxt = Files.testTxt.url(locale: Locale.current)
let localeVideo = Files.aVideoWithSpacesMp4.path(locale: Locale.current)

Fonts

fonts:
  inputs: /path/to/font/dir
  outputs:
    templateName: swift5
    output: Fonts.swift

This will recursively go through the specified directory, finding any typeface files (TTF, OTF, …), defining a struct FontFamily for each family, and an enum nested under that family that will represent the font styles.

Example of code generated by the bundled template

internal enum FontFamily {
  internal enum SFNSDisplay: String, FontConvertible {
    internal static let regular = FontConvertible(name: ".SFNSDisplay-Regular", family: ".SF NS Display", path: "SFNSDisplay-Regular.otf")
  }
  internal enum ZapfDingbats: String, FontConvertible {
    internal static let regular = FontConvertible(name: "ZapfDingbatsITC", family: "Zapf Dingbats", path: "ZapfDingbats.ttf")
  }
}

Usage Example

// You can create fonts with the convenience constructor like this:
let displayRegular = UIFont(font: FontFamily.SFNSDisplay.regular, size: 20.0) // iOS
let dingbats = NSFont(font: FontFamily.ZapfDingbats.regular, size: 20.0)  // macOS

// Or as an alternative, you can refer to enum instance and call .font on it:
let sameDisplayRegular = FontFamily.SFNSDisplay.regular.font(size: 20.0)
let sameDingbats = FontFamily.ZapfDingbats.regular.font(size: 20.0)

Interface Builder

ib:
  inputs: /dir/to/search/for/storyboards
  outputs:
    - templateName: scenes-swift5
      output: Storyboard Scenes.swift
    - templateName: segues-swift5
      output: Storyboard Segues.swift

This will generate an enum for each of your NSStoryboard/UIStoryboard, with respectively one static let per storyboard scene or segue.

Example of code generated by the bundled template

The generated code will look like this:

// output from the scenes template

internal enum StoryboardScene {
  internal enum Dependency: StoryboardType {
    internal static let storyboardName = "Dependency"

    internal static let dependent = SceneType<UIViewController>(storyboard: Dependency.self, identifier: "Dependent")
  }
  internal enum Message: StoryboardType {
    internal static let storyboardName = "Message"

    internal static let messagesList = SceneType<UITableViewController>(storyboard: Message.self, identifier: "MessagesList")
  }
}

// output from the segues template

internal enum StoryboardSegue {
  internal enum Message: String, SegueType {
    case customBack = "CustomBack"
    case embed = "Embed"
    case nonCustom = "NonCustom"
    case showNavCtrl = "Show-NavCtrl"
  }
}

Usage Example

// You can instantiate scenes using the `instantiate` method:
let vc = StoryboardScene.Dependency.dependent.instantiate()

// You can perform segues using:
vc.perform(segue: StoryboardSegue.Message.embed)

// or match them (in prepareForSegue):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  switch StoryboardSegue.Message(segue) {
  case .embed?:
    // Prepare for your custom segue transition, passing information to the destination VC
  case .customBack?:
    // Prepare for your custom segue transition, passing information to the destination VC
  default:
    // Other segues from other scenes, not handled by this VC
    break
  }
}

JSON and YAML

json:
  inputs: /path/to/json/dir-or-file
  outputs:
    templateName: runtime-swift5
    output: JSON.swift
yaml:
  inputs: /path/to/yaml/dir-or-file
  outputs:
    templateName: inline-swift5
    output: YAML.swift

This will parse the given file, or when given a directory, recursively search for JSON and YAML files. It will define an enum for each file (and documents in a file where needed), and type-safe constants for the content of the file.

Unlike other parsers, this one is intended to allow you to use more custom inputs (as the formats are quite open to your needs) to generate your code. This means that for these parsers (and the plist one), you'll probably be more likely to use custom templates to generate code properly adapted/tuned to your inputs, rather than using the bundled templates. To read more about writing your own custom templates, see see the dedicated documentation.

Example of code generated by the bundled template

internal enum JSONFiles {
  internal enum Info {
    private static let _document = JSONDocument(path: "info.json")
    internal static let key1: String = _document["key1"]
    internal static let key2: String = _document["key2"]
    internal static let key3: [String: Any] = _document["key3"]
  }
  internal enum Sequence {
    internal static let items: [Int] = objectFromJSON(at: "sequence.json")
  }
}

Usage Example

// This will be a dictionary
let foo = JSONFiles.Info.key3

// This will be an [Int]
let bar = JSONFiles.Sequence.items

Plists

plist:
  inputs: /path/to/plist/dir-or-file
  outputs:
    templateName: runtime-swift5
    output: Plist.swift

This will parse the given file, or when given a directory, recursively search for Plist files. It will define an enum for each file (and documents in a file where needed), and type-safe constants for the content of the file.

Unlike other parsers, this one is intended to allow you to use more custom inputs (as the format is quite open to your needs) to generate your code. This means that for this parser (and the json and yaml ones), you'll probably be more likely to use custom templates to generate code properly adapted/tuned to your inputs, rather than using the bundled templates. To read more about writing your own custom templates, see see the dedicated documentation.

Example of code generated by the bundled template

internal enum PlistFiles {
  internal enum Test {
    internal static let items: [String] = arrayFromPlist(at: "array.plist")
  }
  internal enum Stuff {
    private static let _document = PlistDocument(path: "dictionary.plist")
    internal static let key1: Int = _document["key1"]
    internal static let key2: [String: Any] = _document["key2"]
  }
}

Usage Example

// This will be an array
let foo = PlistFiles.Test.items

// This will be an Int
let bar = PlistFiles.Stuff.key1

Strings

strings:
  inputs: /path/to/language.lproj
  outputs:
    templateName: structured-swift5
    output: Strings.swift

This will generate a Swift enum L10n that will map all your Localizable.strings and Localizable.stringsdict (or other tables) keys to a static let constant. And if it detects placeholders like %@,%d,%f, it will generate a static func with the proper argument types instead, to provide type-safe formatting. By default it will add comments to the generated constants and functions using the comments from the strings file if present, or the default translation of the string.

Note that all dots within the key names are converted to dots in code (by using nested enums). You can provide a different separator than . to split key names into substructures by using a parser option – see the parser documentation.

Example of code generated by the structured bundled template

Given the following Localizable.strings file:

/* Title for an alert */
"alert_title" = "Title of the alert";
"alert_message" = "Some alert body there";
/* A comment with no space above it */
"bananas.owner" = "Those %d bananas belong to %@.";

And the following Localizable.stringsdict file:

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
  <dict>
    <key>apples.count</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%#@apples@</string>
        <key>apples</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>zero</key>
            <string>You have no apples</string>
            <key>one</key>
            <string>You have one apple</string>
            <key>other</key>
            <string>You have %d apples. Wow that is a lot!</string>
        </dict>
    </dict>
  </dict>
</plist>

Reminder: Don't forget to end each line in your *.strings files with a semicolon ;! Now that in Swift code we don't need semi-colons, it's easy to forget it's still required by the Localizable.strings file format 😉

The generated code will contain this:

internal enum L10n {
  /// Some alert body there
  internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"Some alert body there"#)
  /// Title for an alert
  internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"Title of the alert"#)
  internal enum Apples {
    /// You have %d apples
    internal static func count(_ p1: Int) -> String {
      return L10n.tr("Localizable", "apples.count", p1, fallback: #"You have %d apples"#)
    }
  }
  internal enum Bananas {
    /// A comment with no space above it
    internal static func owner(_ p1: Int, _ p2: Any) -> String {
      return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"Those %d bananas belong to %@."#)
    }
  }
}

Note that if the same key is present in both the .strings and the .stringsdict files, SwiftGen will only consider the one in the .stringsdict file, as that's also how Foundation behaves at runtime.

Usage Example

Once the code has been generated by the script, you can use it this way in your Swift code:

// Simple strings
let message = L10n.alertMessage
let title = L10n.alertTitle

// with parameters, note that each argument needs to be of the correct type
let apples = L10n.Apples.count(3)
let bananas = L10n.Bananas.owner(5, "Olivier")

Flat Strings Support

SwiftGen also has a template to support flat strings files (i.e. without splitting the keys in substructures using "dot syntax"). The advantage is that your keys won't be mangled in any way; the disadvantage is that auto-completion won't be as nice.

Example of code generated by the flat bundled template

internal enum L10n {
  /// Some alert body there
  internal static let alertMessage = L10n.tr("Localizable", "alert__message", fallback: #"Some alert body there"#)
  /// Title for an alert
  internal static let alertTitle = L10n.tr("Localizable", "alert__title", fallback: #"Title of the alert"#)
  /// You have %d apples
  internal static func applesCount(_ p1: Int) -> String {
    return L10n.tr("Localizable", "apples.count", p1, fallback: #"You have %d apples"#)
  }
  /// A comment with no space above it
  internal static func bananasOwner(_ p1: Int, _ p2: Any) -> String {
    return L10n.tr("Localizable", "bananas.owner", p1, String(describing: p2), fallback: #"Those %d bananas belong to %@."#)
  }
}

Given the same Localizable.strings and Localizable.stringsdict as above the usage will now be:

// Simple strings
let message = L10n.alertMessage
let title = L10n.alertTitle

// with parameters, note that each argument needs to be of the correct type
let apples = L10n.applesCount(3)
let bananas = L10n.bananasOwner(5, "Olivier")

Licence

This code and tool is under the MIT Licence. See the LICENCE file in this repository.

Attributions

This tool is powered by

It is currently mainly maintained by @AliSoftware and @djbe. But I couldn't thank enough all the other contributors to this tool along the different versions which helped make SwiftGen awesome! 🎉

If you want to contribute, don't hesitate to open a Pull Request, or even join the team!

Other Libraries / Tools

If you want to also get rid of String-based APIs not only for your resources, but also for UITableViewCell, UICollectionViewCell and XIB-based views, you should take a look at my Mixin Reusable.

If you want to generate Swift code from your own Swift code (so meta!), like generate Equatable conformance to your types and a lot of other similar things, use Sourcery.

SwiftGen and Sourcery are complementary tools. In fact, Sourcery uses Stencil too, as well as SwiftGen's StencilSwiftKit so you can use the exact same syntax for your templates for both!

You can also follow me on twitter for news/updates about other projects I am creating, or read my blog.

Download Details:

Author: SwiftGen
Source Code: https://github.com/SwiftGen/SwiftGen 
License: MIT license

#swift #ios #localization #codegenerators 

SwiftGen: The Swift Code Generator for Your Assets, Storyboards

A Simple Text Widget with Flutter

Bye Bye Localization 👋

Its just a simple Text widget that will provide automatic fast translation for any text. it supports 59 languages.

No more boring Localization configuration files, all you have to do is change your old Text() widget to TranslatedText() and enjoy the sweet sweet life without Localization

PS: the translation will be fast as it uses translation model deployed to the device under the hood.

Demo 📹

Demo

How to use ?

Step 1: All you have to do is call the widget

TranslatedText('your text')

Step2: don't forget to call init the widget using the Manager

  Future<bool> initWidget() async {
    return await TranslationManager().init(
      originLanguage: Languages.ENGLISH,
      translateToLanguage: Languages.ARABIC,
    );
  } 

Step 3: Buy me Coffee 🙂! ☕️

Buy Me a Coffee

coffee

Quick example ⚡️

Widget buildWidget(){
    return FutureBuilder(
      future: initTranslation(),
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Text('${snapshot.error}');
        }
        if (snapshot.connectionState == ConnectionState.done) {
          return TranslatedText('I am a text that will be translated always');
        }
        return CircularProgressIndicator();
      },
    );
  }
  Future<bool> initWidget() async {
    return await TranslationManager().init(
      originLanguage: Languages.ENGLISH,
      /// Change here the language to change the translation, and re run.
      /// For example Languages.RUSSIAN.
      translateToLanguage: Languages.ARABIC,
    );
  }

Live Demo 🔆

below you will find a link to APK contains live demo

Download APK

Author ✍️

Muwaffaq Imam

  • Edu email : m.imam@innopolis.university
  • Telegram : moofiy

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add bye_bye_localization

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  bye_bye_localization: ^1.0.0+8

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:bye_bye_localization/bye_bye_localization.dart'; 

example/lib/main.dart

import 'dart:ui' as ui;

import 'package:bye_bye_localization/bye_bye_localization.dart';
import 'package:flutter/material.dart';
import 'dart:async';

void main() => runApp(PdfExtractionMain());

class PdfExtractionMain extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: PdfExtraction(),
    );
  }
}

class PdfExtraction extends StatefulWidget {
  @override
  _PdfExtractionState createState() => _PdfExtractionState();
}

class _PdfExtractionState extends State<PdfExtraction> {
  static final String _startingText =
      "A simple Text Widget, that can translate any text to any language using instant on device translation AI model, all you have to do is to provide the text and the widget will translate automatically, as a result you don't have to specify and localization files and type translation manually the widget will do it for you.";
  String _text = _startingText;
  Map<String, String>? originLanguage = {'ENGLISH': "en"};
  Map<String, String>? translateTo;
  bool _translate = true;
  bool textDirection = false;


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title:  TranslatedText(
            'Bye Bye Localization',
            style: TextStyle(fontSize: 24),
          ),
        ),
        body:
        FutureBuilder(
          // Initialize FlutterFire:
          future: initTranslation(),
          builder: (context, snapshot) {
            // Check for errors
            if (snapshot.hasError) {
              return Text('${snapshot.error}');
            }

            // Once complete, show your application
            if (snapshot.connectionState == ConnectionState.done) {
              return buildBody();
            }

            // Otherwise, show something whilst waiting for initialization to complete
            return Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Center(
                    child: RichText(
                      text: TextSpan(
                        text:
                            'Translating  from ${originLanguage!.keys.first} to ${translateTo == null ? Localizations.localeOf(context).languageCode : translateTo!.keys.first} \n',
                        style: TextStyle(fontSize: 30, color: Colors.black),
                        children: const <TextSpan>[
                          TextSpan(
                              text: 'this might take a while... \n',
                              style: TextStyle(
                                  fontWeight: FontWeight.bold, fontSize: 20)),
                          TextSpan(
                              text:
                                  'because we are downloading an AI model to translate through it,'
                                  ' once finished you will see an incredible thing, I PROMISE... \n ',
                              style: TextStyle(
                                  fontWeight: FontWeight.bold, fontSize: 16)),
                          TextSpan(
                              text:
                                  '  so say Astaghfer Allah in this time time!',
                              style: TextStyle(
                                  fontFamily: 'casual', fontSize: 30)),
                        ],
                      ),
                    ),
                  ),
                  CircularProgressIndicator(
                    strokeWidth: 5,
                  ),
                ],
              ),
            );
          },
        )
    );
  }

  TextEditingController _controller = new TextEditingController();

  Container buildBody() {
    return Container(
      padding: EdgeInsets.all(10),
      child: ListView(
        primary: true,
        shrinkWrap: true,
        children: <Widget>[
          ListTile(
            onTap: () {
              buildModelSheet().then((value) {
                setState(() {
                  translateTo = value;
                });
              });
            },
            leading: Icon(Icons.translate),
            trailing: Icon(Icons.arrow_forward_ios_rounded),
            title: Text(
              "tap to Change Local",
              style: TextStyle(color: Colors.black),
            ),
            subtitle: translateTo != null
                ? Text('${translateTo!.keys.first}')
                : Text('current local is -->'
                    '${LanguageHelper.languages.firstWhere(
                          (k) =>
                              k.values.first == ui.window.locale.languageCode,
                        ).keys.first}'),
          ),
          Divider(),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 8.0),
            child: TextFormField(
              controller: _controller,
              decoration: const InputDecoration(
                icon: Icon(Icons.edit_rounded),
                hintText: 'Write any text then press translate',
              ),
            ),
          ),
          TextButton(
            child: TranslatedText(
              "Translate the text",
              style: TextStyle(color: Colors.white),
            ),
            style: TextButton.styleFrom(
                padding: EdgeInsets.all(5), backgroundColor: Colors.blueAccent),
            onPressed: () {
              setState(() {
                _translate = true;
                _text = _controller.text.isNotEmpty
                    ? _controller.text
                    : _startingText;
              });
            },
          ),
          TextButton(
            child: TranslatedText(
              "Change Text direction",
              style: TextStyle(color: Colors.white),
            ),
            style: TextButton.styleFrom(
                padding: EdgeInsets.all(5), backgroundColor: Colors.blueAccent),
            onPressed: () {
              setState(() {
                textDirection = !textDirection;
              });
            },
          ),
          TextButton(
            child: Text(
              _translate ? "Show Original language" : "Show translation",
              style: TextStyle(color: Colors.white),
            ),
            style: TextButton.styleFrom(
                padding: EdgeInsets.all(5), backgroundColor: Colors.blueAccent),
            onPressed: () {
              setState(() {
                _translate = !_translate;
              });
            },
          ),
          TextButton(
            child: TranslatedText(
              'Reset text',
              style: TextStyle(color: Colors.white),
            ),
            style: TextButton.styleFrom(
                padding: EdgeInsets.all(5), backgroundColor: Colors.blueAccent),
            onPressed: () {
              setState(() {
                _text = _startingText;
              });
            },
          ),
          _translate
              ? TranslatedText(_text,
                  textDirection:
                      textDirection ? TextDirection.ltr : TextDirection.rtl,
                  style: TextStyle(
                    fontSize: 18,
                  ))
              : Text(
                  _text,
                  textDirection:
                      textDirection ? TextDirection.ltr : TextDirection.rtl,
                  style: TextStyle(
                    fontSize: 18,
                  ),
                ),
        ],
      ),
    );
  }

  Future<bool> initTranslation() async {
    Locale myLocale = Localizations.localeOf(context);
    print('myLocale.languageCode ${ui.window.locale.languageCode}');
    return await TranslationManager().init(
        translateToLanguage: translateTo == null
            ? ui.window.locale.languageCode
            : translateTo!.values.first,
        originLanguage: originLanguage!.values.first);
  }

  Future<bool> initWidget() async {
    return await TranslationManager().init(
      originLanguage: Languages.ENGLISH,
      translateToLanguage: Languages.ARABIC,
    );
  }

  Future<Map<String, String>?> buildModelSheet() async {
    return await showModalBottomSheet<Map<String, String>>(
      enableDrag: true,
      context: context,
      builder: (BuildContext context) {
        return Container(
          height: 400,
          color: Colors.amberAccent,
          child: Center(
            child: ListView.builder(
              itemCount: LanguageHelper.languages.length,
              itemBuilder: (BuildContext context, int index) {
                return Column(
                  children: [
                    ListTile(
                      onTap: () {
                        Navigator.pop(context, LanguageHelper.languages[index]);
                      },
                      title: Text(
                        '${LanguageHelper.languages[index].keys.first}',
                        style: TextStyle(fontSize: 16),
                      ),
                    ),
                    Divider(),
                  ],
                );
              },
            ),
          ),
        );
      },
    );
  }
} 

Download Details:

Author: MuwaffaqImam

Source Code: https://github.com/MuwaffaqImam/bye-bye-localization

#flutter #widget #localization 

A Simple Text Widget with Flutter

Flutter Translator Package Use for App Localization

Flutter Translator

Flutter Translator is a package use for in-app localization with Map data. More easier and faster to implement. This package is inspired by the flutter_localizations itself. Follow the step below to use the package or checkout a small complete example project of the package.

How To Use

Prepare language source (Map<String, dynamic>)

Create a dart file which will contain all the Map data of the locale language your app need. You can change the file name, class name, and file path whatever you like. Example:

mixin AppLocale {
  static const String title = 'title';

  static const Map<String, dynamic> EN = {title: 'Localization'};
  static const Map<String, dynamic> KM = {title: 'ការធ្វើមូលដ្ឋានីយកម្ម'};
  static const Map<String, dynamic> JA = {title: 'ローカリゼーション'};
}

Project configuration

  • Initialize the TranslatorGenerator object (this can be local or global, up to you)
final TranslatorGenerator translator = TranslatorGenerator.instance;
  • Init the list of MapLocale and startup language for the app. This has to be done only at the main.dart or the first MaterialApp in your project.
@override
void initState() {
    translator.init(
        mapLocales: [
            MapLocale('en', AppLocale.EN),
            MapLocale('km', AppLocale.KM),
            MapLocale('ja', AppLocale.JA),
        ],
        initLanguageCode: 'en',
    );
    translator.onTranslatedLanguage = _onTranslatedLanguage;
    super.initState();
}

/// the setState function here is a must to add
void _onTranslatedLanguage(Locale? locale) {
    setState(() {});
}
  • Add supportedLocales and localizationsDelegates to the MaterialApp
@override
Widget build(BuildContext context) {
    return MaterialApp(
        supportedLocales: translator.supportedLocales,
        localizationsDelegates: translator.localizationsDelegates,
        home: SettingsScreen(),
    );
}
  • Call the translate function anytime you want to translate the app and provide it with the language code
ElevatedButton(
    child: const Text('English'),
    onPressed: () {
        translator.translate('en');
    },
);
  • To display the value from the Map data, just call the getString function by providing the key of the data or use the getString extension (the AppLocale.title is the constant from mixin class above)
translator.getString(AppLocale.title); 

or

AppLocale.title.getString();
  • You also can get the language name too. If you don't specify the language code for the function, it will return the language name depend on the current app locale
translator.getLanguageName(languageCode: 'en');  // English
translator.getLanguageName(languageCode: 'km');  // ភាសាខ្មែរ
translator.getLanguageName(languageCode: 'ja');  // 日本語

translator.getLanguageName();  // get language name depend on current app locale

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_translator

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_translator: ^0.1.7

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_translator/flutter_translator.dart'; 

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_translator/flutter_translator.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final TranslatorGenerator _translator = TranslatorGenerator.instance;

  @override
  void initState() {
    _translator.init(
      mapLocales: [
        MapLocale('en', AppLocale.EN),
        MapLocale('km', AppLocale.KM),
        MapLocale('ja', AppLocale.JA),
      ],
      initLanguageCode: 'en',
    );
    _translator.onTranslatedLanguage = _onTranslatedLanguage;
    super.initState();
  }

  void _onTranslatedLanguage(Locale? locale) {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      supportedLocales: _translator.supportedLocales,
      localizationsDelegates: _translator.localizationsDelegates,
      home: SettingsScreen(),
    );
  }
}

class SettingsScreen extends StatefulWidget {
  @override
  _SettingsScreenState createState() => _SettingsScreenState();
}

class _SettingsScreenState extends State<SettingsScreen> {
  final TranslatorGenerator _translator = TranslatorGenerator.instance;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Text(_translator
              .getString(AppLocale.title))), // or AppLocale.title.getString()
      body: Container(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Current language is: ${_translator.getLanguageName()}'),
            const SizedBox(height: 64.0),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    child: const Text('English'),
                    onPressed: () {
                      _translator.translate('en');
                    },
                  ),
                ),
                const SizedBox(width: 8.0),
                Expanded(
                  child: ElevatedButton(
                    child: const Text('ភាសាខ្មែរ'),
                    onPressed: () {
                      _translator.translate('km');
                    },
                  ),
                ),
                const SizedBox(width: 8.0),
                Expanded(
                  child: ElevatedButton(
                    child: const Text('日本語'),
                    onPressed: () {
                      _translator.translate('ja', save: false);
                    },
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

mixin AppLocale {
  static const String title = 'title';

  static const Map<String, dynamic> EN = {title: 'Localization'};
  static const Map<String, dynamic> KM = {title: 'ការធ្វើមូលដ្ឋានីយកម្ម'};
  static const Map<String, dynamic> JA = {title: 'ローカリゼーション'};
} 

Download Details:

Author: channdara

Source Code: https://github.com/channdara/flutter_translator

#flutter #localization 

Flutter Translator Package Use for App Localization
Dexter  Goodwin

Dexter Goodwin

1663066980

5 Favorite JavaScript I18n And L10n Libraries

In today's post we will learn about 5 Favorite JavaScript I18n And L10n Libraries.

Internationalization (i18n) is the process of developing products in such a way that they can be localized for languages and cultures easily. Localization (l10n), is the process of adapting applications and text to enable their usability in a particular cultural or linguistic market. For application developers, internationalizing an application means abstracting all of the strings and other locale-specific bits (such as date or currency formats) out of the application. Localizing an application means providing translations and localized formats for the abstracted bits.

Table of contents:

  • i18next - Internationalisation (i18n) with JavaScript the easy way.
  • Polyglot - Tiny i18n helper library.
  • Babelfish - i18n with human friendly API and built in plurals support.
  • TTag - Modern javascript i18n localization library based on ES6 tagged templates and the good old GNU gettext.
  • Attranslate - A JavaScript-tool for synchronizing translation-files, including JSON/YAML/XML and other formats.

1 - i18next: Internationalisation (i18n) with JavaScript the easy way.

i18next is a very popular internationalization framework for browser or any other javascript environment (eg. Node.js, Deno).

i18next provides:

For more information visit the website:

Our focus is providing the core to building a booming ecosystem. Independent of the building blocks you choose, be it react, angular or even good old jquery proper translation capabilities are just one step away.

View on Github

2 - Polyglot: Tiny i18n helper library.

Polyglot.js is a tiny I18n helper library written in JavaScript, made to work both in the browser and in CommonJS environments (Node). It provides a simple solution for interpolation and pluralization, based off of Airbnb’s experience adding I18n functionality to its Backbone.js and Node apps.

Installation

install with npm:

$ npm install node-polyglot

Running the tests

Clone the repo, run npm install, and npm test.

Usage

Instantiation

First, create an instance of the Polyglot class, which you will use for translation.

var polyglot = new Polyglot();

Polyglot is class-based so you can maintain different sets of phrases at the same time, possibly in different locales. This is very useful for example when serving requests with Express, because each request may have a different locale, and you don’t want concurrent requests to clobber each other’s phrases.

See Options Overview for information about the options object you can choose to pass to new Polyglot.

Translation

Tell Polyglot what to say by simply giving it a phrases object, where the key is the canonical name of the phrase and the value is the already-translated string.

polyglot.extend({
  "hello": "Hello"
});

polyglot.t("hello");
=> "Hello"

You can also pass a mapping at instantiation, using the key phrases:

var polyglot = new Polyglot({phrases: {"hello": "Hello"}});

Polyglot doesn’t do the translation for you. It’s up to you to give it the proper phrases for the user’s locale.

View on Github

3 - Babelfish: i18n with human friendly API and built in plurals support.

Internationalization with easy syntax for node.js and browser.

Classic solutions use multiple phrases for plurals. Babelfish defines plurals inline instead - that's more compact, and easy for programmers. Also, phrases are grouped into nested scopes, like in Ruby.

BabelFish supports all plural rules from unicode CLDR (via plurals-cldr).

Installation

node.js:

$ npm install babelfish

browser:

$ bower install babelfish

Use es5-shim for old browsers compatibility.

Phrases Syntax

  • #{varname} Echoes value of variable
  • ((Singular|Plural1|Plural2)):count Plural form

example:

  • А у меня в кармане #{nails_count} ((гвоздь|гвоздя|гвоздей)):nails_count

You can also omit anchor variable for plurals, by default it will be count. Thus following variants are equal:

  • I have #{count} ((nail|nails))
  • I have #{count} ((nail|nails)):count

Also you can use variables in plural parts:

  • I have ((#{count} nail|#{count} nails))

Need special zero form or overwrite any specific value? No problems:

  • I have ((=0 no nails|#{count} nail|#{count} nails))

View on Github

4 - TTag: Modern javascript i18n localization library based on ES6 tagged templates and the good old GNU gettext.

Modern javascript i18n localization library based on ES6 tagged templates and the good old GNU gettext.

Usage example

import { t, ngettext, msgid } from 'ttag'

// formatted strings
const name = 'Mike';
const helloMike = t`Hello ${name}`;

// plurals (works for en locale out of the box)
const n = 5;
const msg = ngettext(msgid`${ n } task left`, `${ n } tasks left`, n)

Installation

npm install --save ttag

CLI

You may also need to install ttag-cli for po files manipulation.

ttag cli - https://github.com/ttag-org/ttag-cli

npm install --save-dev ttag-cli

Usage from CDN

https://unpkg.com/ttag/dist/ttag.min.js

This project is designed to work in pair with babel-plugin-ttag.
But you can also play with it without transpilation.

View on Github

5 - Attranslate: A JavaScript-tool for synchronizing translation-files, including JSON/YAML/XML and other formats.

attranslate is a tool for synchronizing translation-files, including JSON/YAML/XML and other formats. attranslate is optimized for smooth rollouts in hectic project environments, even if you already have many translations. Optionally, attranslate works with automated translation-services. For example, let's say that a translation-service achieves 80% correct translations. With attranslate, a quick fix of the remaining 20% may be faster than doing everything by hand. Other than that, attranslate supports purely manual translations or even file-format-conversions without changing the language.

Why attranslate?

In contrast to paid services, a single developer can integrate attranslate in a matter of minutes. attranslate can operate on the very same translations-files that you are already using. This is possible because attranslate operates on your file in a surgical way, with as little changes as possible.

Features

Cross-platform Support

attranslate is designed to translate any website or app with any toolchain. attranslate works for i18n/JavaScript-frameworks/Android/iOS/Flutter/Ruby/Jekyll/Symfony/Django/WordPress and many other platforms. To make this possible, attranslate supports the following file formats:

  • Flat or nested JSON
  • Flat or nested YAML
  • PO/POT-files
  • Android-XML or any other XMLs with text-contents
  • iOS-Strings
  • Flutter-ARB
  • CSV (e.g. for Google Docs or Microsoft Excel)

Preserve Manual Translations

attranslate recognizes that automated translations are not perfect. Therefore, whenever you are unhappy with the produced results, attranslate allows you to simply overwrite texts in your target-files. attranslate will never ever overwrite a manual correction in subsequent runs.

Optionally Overwrite Outdated Translations

attranslate is capable of detecting outdated translations. Overwriting outdated translations helps to ensure the freshness of translations. However, in hectic project environments, it might be easier to leave outdated translations as-is. Therefore, attranslate leaves outdated translations as-is unless you explicitly configure it to overwrite them.

Available Services

attranslate supports the following translation-services:

  • manual: Translate texts manually by entering them into attranslate.
  • Google Cloud Translate
  • Azure Translator
  • sync-without-translate: Does not change the language. This can be useful for converting between file formats, or for maintaining region-specific differences.

View on Github

Thank you for following this article.

Related videos:

i18next Crash Course | the JavaScript i18n framework

#javascript #i18n #localization 

5 Favorite JavaScript I18n And L10n Libraries

A Minimal CSV Localization Package for Flutter

csv_localizations

A minimal CSV localization package for Flutter.

Store translations for multiple languages in a single CSV file. One language per column. One translation per row.

Usage

See example.

Install

Add to your pubspec.yaml

dependencies:
  csv_localizations:

Add a single CSV asset file

Add a single CSV file as an asset and describe it in your pubspec.yaml

flutter:
  assets:
    - assets/translations.csv

Example CSV file

key,en,nb,en-GB,en-US
Hi,Hi,Hei,Hi GB,Hi US
my_img,assets/en.png,assets/nb.png,assets/en.png,assets/en.png
Multiline,"This is a
  multiline string","Denne teksten går over
  flere linjer","This is a
  multiline string","This is a
  multiline string"

Tip: keys can point to local assets like images etc.

Format

keyen-GBennb
HiHiHiHei
BikeBikeBikeSykkel
DogDogDogHund

First row lists supported language/country codes.

First column are keys for localized values.

You can wrap multiline strings in quotation marks.

API

Translate strings using:

CsvLocalizations.instance.string('Hi')

Or add a String extension:

Let's keep the API simple and not pollute the String API

extension LocalizedString on String {
  String tr(BuildContext context) => CsvLocalizations.instance.string(this);
}

We use \n as the default eol (end-of-line) char, but you can set this via CsvLocalizations.instance.eol.

Check if the translation file is loaded using CsvLocalizations.instance.loaded. Only necessary if called before initializing the global localizationDelegates.

MaterialApp

Add CsvLocalizationsDelegate to MaterialApp and set supportedLocales using language codes.

MaterialApp(
  localizationsDelegates: [
    ... // global delegates
    CsvLocalizationsDelegate('assets/translations.csv'),
  ],
  supportedLocales: [
    Locale('en', 'GB'),
    Locale('en', 'US'),
    Locale('en'),
    Locale('nb'),
  ],
}

Note on iOS

Add supported languages to ios/Runner/Info.plist as described here.

Example:

<key>CFBundleLocalizations</key>
<array>
	<string>en</string>
	<string>en-US</string>
	<string>en-GB</string>
	<string>nb</string>
</array>

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add csv_localizations

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  csv_localizations: ^1.5.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:csv_localizations/csv_localizations.dart'; 

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:csv_localizations/csv_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'string_ext.dart';

void main() {
  CsvLocalizations.instance.eol = '\n';

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: const [
        ...GlobalMaterialLocalizations.delegates,
        CsvLocalizationsDelegate('assets/translations.csv'),
      ],
      supportedLocales: const [
        Locale('en', 'GB'),
        Locale('en', 'US'),
        Locale('en'),
        Locale('nb'),
      ],
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('csv_localizations'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text(
            'Hi'.tr,
            style: const TextStyle(
              fontSize: 24,
            ),
          ),
          Image.asset(
            'my_img'.tr,
            width: 64,
            height: 64,
            fit: BoxFit.contain,
            filterQuality: FilterQuality.none,
          ),
          Text(
            'Multiline'.tr,
            style: const TextStyle(
              fontSize: 24,
            ),
          ),
        ],
      ),
    );
  }
} 

Download Details:

Author: erf

Source Code: https://github.com/erf/csv_localizations

#flutter #localization 

A Minimal CSV Localization Package for Flutter
Rupert  Beatty

Rupert Beatty

1659280020

Linguist: Multilingual Urls and Redirects for Laravel

Linguist - Multilingual urls and redirects for Laravel

This package provides an easy multilingual urls and redirection support for the Laravel framework.

In short Laravel will generate localized urls for links and redirections.

route('people') 
http://site.com/people
http://site.com/fr/people

Linguist works perfectly well with https://github.com/tightenco/ziggy named Laravel routes for javascript package!

Installation

Linguist is very easy to use. The locale slug is removed from the REQUEST_URI leaving the developer with the cleanest multilingual environment possible.

Install using Composer:

composer require keevitaja/linguist

There are several options to make Linguist work.

Option 1: Modify the public/index.php

Add following line after the vendor autoloading to your projects public/index.php file.

(new Keevitaja\Linguist\UriFixer)->fixit();

End result would be this:

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/

require __DIR__.'/../vendor/autoload.php';

(new Keevitaja\Linguist\UriFixer)->fixit();

Option 2: Use LocalizedKernel

Note: This option works only if you have not changed your applications root namespace. Default is App.

In your projects bootstrap/app.php swap the App\Http\Kernel with Keevitaja\Linguist\LocalazedKernel:

/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    //App\Http\Kernel::class
    Keevitaja\Linguist\LocalizedKernel::class
);

Option 3: modify the App\Http\Kernel

Note: This also works with custom root namespace.

<?php

namespace App\Http;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Routing\Router;
use Keevitaja\Linguist\UriFixer;

class Kernel extends HttpKernel
{

    public function __construct(Application $app, Router $router)
    {
        (new UriFixer)->fixit();

        parent::__construct($app, $router);
    }

Publish config

Finally you need to publish the Linguist config to set your enabled locales and other relavant configurations.

php artisan vendor:publish --provider="Keevitaja\Linguist\LinguistServiceProvider"

Your personal configuration file will be config/linguist.php.

Usage

You can add the LocalizeUrls middleware your web middleware group as the first item to get the linguist support:

/**
 * The application's route middleware groups.
 *
 * @var array
 */
protected $middlewareGroups = [
    'web' => [
        \Keevitaja\Linguist\LocalizeUrls::class,

Note: This middleware has to be the first item in group!

Another option is to use Linguist in your applications service provider:

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot(\Keevitaja\Linguist\Linguist $linguist)
    {
        $linguist->localize();
    }

UrlGenerator will add the locale slug in front of the URI when needed. No extra actions needed.

Route::get('people', ['as' => 'people.index', 'uses' => ''PeopleController@index'']);
{{ route('people.index') }} or {{ url('people') }}
http://site.com/people // default locale from linguist config
http://site.com/fr/people
http://site.com/ru/people

Switcher is a little helper to get the current URLs for the locale switcher.

$urls = dispatch_now(new \Keevitaja\Linguist\Switcher);

NB! Both config and route caching are working!

Assets

Use linguist helpers for a correct routing of assets

Regular Assets

<link rel="stylesheet" href="{{ linguist_asset('css/style.css') }}">
<script type="text/javascript" src="{{ linguist_asset('js/my_js.js') }}"></script>

Secure Assets

<link rel="stylesheet" href="{{ secure_linguist_asset('css/style.css') }}">
<script type="text/javascript" src="{{ secure_linguist_asset('js/my_js.js') }}"></script>

Queues

To make localization work in queues you need to run Linguist->localize($theLocaleYouWant) inside the queued item.

Author: Keevitaja
Source Code: https://github.com/keevitaja/linguist 
License: MIT license

#laravel #i18n #php #translation #localization 

Linguist: Multilingual Urls and Redirects for Laravel
Rupert  Beatty

Rupert Beatty

1659238980

Laravel-localization: Easy Localization for Laravel

Laravel Localization

Easy i18n localization for Laravel, an useful tool to combine with Laravel localization classes.

The package offers the following:

  • Detect language from browser
  • Smart redirects (Save locale in session/cookie)
  • Smart routing (Define your routes only once, no matter how many languages you use)
  • Translatable Routes
  • Supports caching & testing
  • Option to hide default locale in url
  • Many snippets and helpers (like language selector)

Laravel compatibility

Laravellaravel-localization
4.0.x0.13.x
4.1.x0.13.x
4.2.x0.15.x
5.0.x/5.1.x1.0.x
5.2.x-5.4.x (PHP 7 not required)1.2.x
5.2.x-5.8.x (PHP 7 required)1.3.x
5.2.0-6.x (PHP 7 required)1.4.x
5.2.0-9.x (PHP 7 required)1.7.x

Installation

Install the package via composer: composer require mcamara/laravel-localization

For Laravel 5.4 and below it necessary to register the service provider.

Config Files

In order to edit the default configuration you may execute:

php artisan vendor:publish --provider="Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider"

After that, config/laravellocalization.php will be created.

The configuration options are:

  • supportedLocales Languages of your app (Default: English & Spanish).
  • useAcceptLanguageHeader If true, then automatically detect language from browser.
  • hideDefaultLocaleInURL If true, then do not show default locale in url.
  • localesOrder Sort languages in custom order.
  • localesMapping Rename url locales.
  • utf8suffix Allow changing utf8suffix for CentOS etc.
  • urlsIgnored Ignore specific urls.

Register Middleware

You may register the package middleware in the app/Http/Kernel.php file:

<?php namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel {
    /**
    * The application's route middleware.
    *
    * @var array
    */
    protected $routeMiddleware = [
        /**** OTHER MIDDLEWARE ****/
        'localize'                => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
        'localizationRedirect'    => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
        'localeSessionRedirect'   => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
        'localeCookieRedirect'    => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
        'localeViewPath'          => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class
    ];
}

Usage

Add the following to your routes file:

// routes/web.php

Route::group(['prefix' => LaravelLocalization::setLocale()], function()
{
    /** ADD ALL LOCALIZED ROUTES INSIDE THIS GROUP **/
    Route::get('/', function()
    {
        return View::make('hello');
    });

    Route::get('test',function(){
        return View::make('test');
    });
});

/** OTHER PAGES THAT SHOULD NOT BE LOCALIZED **/

Once this route group is added to the routes file, a user can access all locales added into supportedLocales (en and es by default). For example, the above route file creates the following addresses:

// Set application language to English
http://url-to-laravel/en
http://url-to-laravel/en/test

// Set application language to Spanish
http://url-to-laravel/es
http://url-to-laravel/es/test

// Set application language to English or Spanish (depending on browsers default locales)
// if nothing found set to default locale
http://url-to-laravel
http://url-to-laravel/test

The package sets your application locale App::getLocale() according to your url. The locale may then be used for Laravel's localization features.

You may add middleware to your group like this:

Route::group(
[
    'prefix' => LaravelLocalization::setLocale(),
    'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]
], function(){ //...
});

Recommendations

1.: It is strongly recommended to use a redirecting middleware. Urls without locale should only be used to determine browser/default locale and to redirect to the localized url. Otherwise, when search engine robots crawl for example http://url-to-laravel/test they may get different language content for each visit. Also having multiple urls for the same content creates a SEO duplicate-content issue.

2.: It is strongly recommended to localize your links, even if you use a redirect middleware. Otherwise, you will cause at least one redirect each time a user clicks on a link. Also, any action url from a post form must be localized, to prevent that it gets redirected to a get request.

Redirect Middleware

The following redirection middleware depends on the settings of hideDefaultLocaleInURL and useAcceptLanguageHeader in config/laravellocalization.php:

LocaleSessionRedirect

Whenever a locale is present in the url, it will be stored in the session by this middleware.

In there is no locale present in the url, then this middleware will check the following

  • If no locale is saved in session and useAcceptLanguageHeader is set to true, compute locale from browser and redirect to url with locale.
  • If a locale is saved in session redirect to url with locale, unless its the default locale and hideDefaultLocaleInURL is set to true.

For example, if a user navigates to http://url-to-laravel/test and en is the current locale, it would redirect him automatically to http://url-to-laravel/en/test.

LocaleCookieRedirect

Similar to LocaleSessionRedirect, but it stores value in a cookie instead of a session.

Whenever a locale is present in the url, it will be stored in the cookie by this middleware.

In there is no locale present in the url, then this middleware will check the following

  • If no locale is saved in cookie and useAcceptLanguageHeader is set to true, compute locale from browser and redirect to url with locale.
  • If a locale is saved in cookie redirect to url with locale, unless its the default locale and hideDefaultLocaleInURL is set to true.

For example, if a user navigates to http://url-to-laravel/test and de is the current locale, it would redirect him automatically to http://url-to-laravel/de/test.

LaravelLocalizationRedirectFilter

When the default locale is present in the url and hideDefaultLocaleInURL is set to true, then the middleware redirects to the url without locale.

For example, if es is the default locale, then http://url-to-laravel/es/test would be redirected to http://url-to-laravel/test and theApp::getLocale() would be set to es.

Helpers

This package comes with a bunch of helpers.

Localized URLs

Localized URLS taken into account route model binding when generating the localized route, aswell as the hideDefaultLocaleInURL and Translated Routes settings.

Get localized URL

    // If current locale is Spanish, it returns `/es/test`
    <a href="{{ LaravelLocalization::localizeUrl('/test') }}">@lang('Follow this link')</a>

Get localized URL for an specific locale

Get current URL in specific locale:

// Returns current url with English locale.
{{ LaravelLocalization::getLocalizedURL('en') }}

Get Clean routes

Returns a URL clean of any localization.

// Returns /about
{{ LaravelLocalization::getNonLocalizedURL('/es/about') }}

Get URL for an specific translation key

Returns a route, localized to the desired locale. If the translation key does not exist in the locale given, this function will return false.

// Returns /es/acerca
{{ LaravelLocalization::getURLFromRouteNameTranslated('es', 'routes.about') }}

Get Supported Locales

Return all supported locales and their properties as an array.

{{ LaravelLocalization::getSupportedLocales() }}

Get Supported Locales Custom Order

Return all supported locales but in the order specified in the configuration file. You can use this function to print locales in the language selector.

{{ LaravelLocalization::getLocalesOrder() }}

Get Supported Locales Keys

Return an array with all the keys for the supported locales.

{{ LaravelLocalization::getSupportedLanguagesKeys() }}

Get Current Locale

Return the key of the current locale.

{{ LaravelLocalization::getCurrentLocale() }}

Get Current Locale Name

Return current locale's name as string (English/Spanish/Arabic/ ..etc).

{{ LaravelLocalization::getCurrentLocaleName() }}

Get Current Locale Native Name

Return current locale's native name as string (English/Español/عربى/ ..etc).

{{ LaravelLocalization::getCurrentLocaleNative() }}

Get Current Locale Direction

Return current locale's direction as string (ltr/rtl).

{{ LaravelLocalization::getCurrentLocaleDirection() }}

Get Current Locale Script

Return the ISO 15924 code for the current locale script as a string; "Latn", "Cyrl", "Arab", etc.

{{ LaravelLocalization::getCurrentLocaleScript() }}

Set view-base-path to current locale

Register the middleware LaravelLocalizationViewPath to set current locale as view-base-path.

Now you can wrap your views in language-based folders like the translation files.

resources/views/en/, resources/views/fr, ...

Map your own custom lang url segments

As you can modify the supportedLocales even by renaming their keys, it is possible to use the string uk instead of en-GB to provide custom lang url segments. Of course, you need to prevent any collisions with already existing keys and should stick to the convention as long as possible. But if you are using such a custom key, you have to store your mapping to the localesMapping array. This localesMapping is needed to enable the LanguageNegotiator to correctly assign the desired locales based on HTTP Accept Language Header. Here is a quick example how to map HTTP Accept Language Header 'en-GB' to url segment 'uk':

// config/laravellocalization.php

'localesMapping' => [
    'en-GB' => 'uk'
],

After that http://url-to-laravel/en-GB/a/b/c becomes http://url-to-laravel/uk/a/b/c.

LaravelLocalization::getLocalizedURL('en-GB', 'a/b/c'); // http://url-to-laravel/uk/a/b/c
LaravelLocalization::getLocalizedURL('uk', 'a/b/c'); // http://url-to-laravel/uk/a/b/c

Creating a language selector

If you're supporting multiple locales in your project you will probably want to provide the users with a way to change language. Below is a simple example of blade template code you can use to create your own language selector.

<ul>
    @foreach(LaravelLocalization::getSupportedLocales() as $localeCode => $properties)
        <li>
            <a rel="alternate" hreflang="{{ $localeCode }}" href="{{ LaravelLocalization::getLocalizedURL($localeCode, null, [], true) }}">
                {{ $properties['native'] }}
            </a>
        </li>
    @endforeach
</ul>

Here default language will be forced in getLocalizedURL() to be present in the URL even hideDefaultLocaleInURL = true.

Note that Route Model Binding is supported.

Translated Routes

You may translate your routes. For example, http://url/en/about and http://url/es/acerca (acerca is about in spanish) or http://url/en/article/important-article and http://url/es/articulo/important-article (article is articulo in spanish) would be redirected to the same controller/view as follows:

It is necessary that at least the localize middleware in loaded in your Route::group middleware (See installation instruction).

For each language, add a routes.php into resources/lang/**/routes.php folder. The file contains an array with all translatable routes. For example, like this:

<?php
// resources/lang/en/routes.php
return [
    "about"    =>  "about",
    "article"  =>  "article/{article}",
];
<?php
// resources/lang/es/routes.php
return [
    "about"    =>  "acerca",
    "article"  =>  "articulo/{article}",
];

You may add the routes in routes/web.php like this:

Route::group(['prefix' => LaravelLocalization::setLocale(),
              'middleware' => [ 'localize' ]], function () {

    Route::get(LaravelLocalization::transRoute('routes.about'), function () {
        return view('about');
    });

    Route::get(LaravelLocalization::transRoute('routes.article'), function (\App\Article $article) {
        return $article;
    });

    //,...
});

Once files are saved, you can access http://url/en/about , http://url/es/acerca , http://url/en/article/important-article and http://url/es/articulo/important-article without any problem.

Translatable route parameters

Maybe you noticed in the previous example the English slug in the Spanish url:

http://url/es/articulo/important-article

It is possible to have translated slugs, for example like this:

http://url/en/article/important-change
http://url/es/articulo/cambio-importante

However, in order to do this, each article must have many slugs (one for each locale). Its up to you how you want to implement this relation. The only requirement for translatable route parameters is, that the relevant model implements the interface LocalizedUrlRoutable.

Implementing LocalizedUrlRoutable

To implement \Mcamara\LaravelLocalization\Interfaces\LocalizedUrlRoutable, one has to create the function getLocalizedRouteKey($locale), which must return for a given locale the translated slug. In the above example, inside the model article, getLocalizedRouteKey('en') should return important-change and getLocalizedRouteKey('es') should return cambio-importante.

Route Model Binding

To use route-model-binding, one should overwrite the function resolveRouteBinding($slug) in the model. The function should return the model that belongs to the translated slug $slug. For example:

public function resolveRouteBinding($slug)
{
        return static::findByLocalizedSlug($slug)->first() ?? abort(404);
}

Tutorial Video

You may want to checkout this video which demonstrates how one may set up translatable route parameters.

Events

You can capture the URL parameters during translation if you wish to translate them too. To do so, just create an event listener for the routes.translation event like so:

Event::listen('routes.translation', function($locale, $attributes)
{
    // Do your magic

    return $attributes;
});

Be sure to pass the locale and the attributes as parameters to the closure. You may also use Event Subscribers, see: http://laravel.com/docs/events#event-subscribers

Caching routes

To cache your routes, use:

php artisan route:trans:cache

... instead of the normal route:cache command. Using artisan route:cache will not work correctly!

For the route caching solution to work, it is required to make a minor adjustment to your application route provision.

In your App's RouteServiceProvider, use the LoadsTranslatedCachedRoutes trait:

<?php
class RouteServiceProvider extends ServiceProvider
{
    use \Mcamara\LaravelLocalization\Traits\LoadsTranslatedCachedRoutes;

For more details see here.

Common Issues

POST is not working

This may happen if you do not localize your action route that is inside your Routes::group. This may cause a redirect, which then changes the post request into a get request. To prevent that, simply use the localize helper.

For example, if you use Auth::routes() and put them into your Route::group Then

<form action="/logout" method="POST">
<button>Logout</button>
</form>

will not work. Instead, one has to use

<form action="{{  \LaravelLocalization::localizeURL('/logout') }} " method="POST">
<button>Logout</button>
</form>

Another way to solve this is to put http method to config to 'laravellocalization.httpMethodsIgnored' to prevent of processing this type of requests

MethodNotAllowedHttpException

If you do not localize your post url and use a redirect middleware, then the post request gets redirected as a get request. If you have not defined such a get route, you will cause this exception.

To localize your post url see the example in POST is not working.

Validation message is only in default locale

This also happens if you did not localize your post url. If you don't localize your post url, the default locale is set while validating, and when returning to back() it shows the validation message in default locale.

To localize your post url see the example in POST is not working.

Testing

During the test setup, the called route is not yet known. This means no language can be set. When a request is made during a test, this results in a 404 - without the prefix set the localized route does not seem to exist.

To fix this, you can use this function to manually set the language prefix:

// TestCase.php
protected function refreshApplicationWithLocale($locale)
{
    self::tearDown();
    putenv(LaravelLocalization::ENV_ROUTE_KEY . '=' . $locale);
    self::setUp();
}

protected function tearDown()
{
    putenv(LaravelLocalization::ENV_ROUTE_KEY);
    parent::tearDown();
}

// YourTest.php
public function testBasicTest()
{
    $this->refreshApplicationWithLocale('en');
    // Testing code
}

Collaborators

Ask mcamara if you want to be one of them!

Changelog

View changelog here -> changelog

Author: mcamara 
Source Code: https://github.com/mcamara/laravel-localization 
License: MIT license

#laravel #localization #php 

Laravel-localization: Easy Localization for Laravel

Intl Utilities to Easily Manage Localization In Dart and Flutter

flutter_jimtl

If you are tired of manually writing ARB files or the limited Flutter localization support you are in the right place.

With this package you'll be able to:

  • Generate an ARB file from Dart code (done by build_runner)
  • Update your translations remotely (from external server or translation service)
  • Have flavors for your translations

Setup

Install last version of jimtl for Dart or flutter_jimtl for Flutter and jimtl_codegen.

dependencies:
  intl:
  flutter_jimtl:

dev_dependencies:
  build_runner: ^2.0.3
  jimtl_codegen:

Your translation with the power of intl package

Intl is a great package to deal with translation, it allow you to write messages, plurals and genders in an easy and safe way.

Let's take some basic examples:

@GenerateArb()
class Translations {
  // accessor for flutter project only
  static Translations of(BuildContext context) => Localizations.of<Translations>(context, Translations)!;

  String get counter => Intl.message('Counter', name: 'counter');

  String get increment => Intl.message('Increment', name: 'increment');

  String counterPushed(int number) => Intl.message('You have pushed the button $number times: ', args: [number], name: 'counterPushed');
}

Did you notice the @GenerateArb() annotation? That where the magic come from. 🪄

Once you run your favourite build_runner command it will generate the ARB corresponding to this class.

This annotation can be customize with the following fields:

  • defaultLocale: locale your working with, default to 'en'
  • defaultFlavor: default flavor of your project, default to 'default'
  • suppressMetaData: suppress meta data when generating the ARB file, default to false
  • suppressLastModified: suppress last modified when generating the ARB file, default to false
  • includeSourceText: whether to include source_text in messages, default to false
  • dir: directory where the ARB should be generated

Flutter project

If you have an existing Flutter project with localization support, then you'll need to declare a LocalizationDelegate in your app.

Here is what it should look like:

localizationsDelegates: [
    DefaultMaterialLocalizations.delegate,
    TranslationsDelegate<Translations>(
      defaultLocale: 'en',
      supportedLocales: ['en', 'fr'],
      // if you are using flavors, you'll need to specify the default and current one
      defaultFlavor: 'default',
      currentFlavor: 'flavor1',
      // This method is called to load the default ARB files, easiest way is to load them from assets
      dataLoader: (locale, flavor) async {
        print('local load $locale and $flavor');
        if (flavor == 'default') {
          return await rootBundle.loadString('assets/arb/translations_$locale.arb');
        }
        return await rootBundle.loadString('assets/arb/translations_${flavor}_$locale.arb');
      },
      // If you want to download your ARB files from a remote server
      // You need to specfiy a custom data loader like this
      updateDataLoader: (locale, flavor) async {
        print('Remote load $locale and $flavor');
        if (locale == 'en' && flavor == 'flavor1') {
            await Future.delayed(Duration(seconds: 10));//simulate some slow network response
            return await rootBundle.loadString('assets/arb/translations_remote_$locale.arb');
        }
        return null;  // no update
      },
      // Once your translations are updated from your remote files this callback will be triggered, you'll need to rebuild in order to see the changes
      onTranslationsUpdated: () {
        print('TX updated, need rebuild');
        setState(() {});
      },
      // Builder to build your custom class containing your translations
      translationsBuilder: () => Translations(),
    ),
]

Here is a little more detail of the TranslationsDelegate parameters:

  • defaultLocale: default locale to use for your app.
  • defaultFlavor: optional default flavor to use for your app, default to 'default'.
  • currentFlavor: optional current flavor to use for your app, default to 'default'.
  • overrideCurrentLocale: optional locale to use for your app, it will override the system locale.
  • dataLoader: callback to get the ARB files for a given locale and flavor, this is call by jimtl to let you provide the ARB files when needed.
  • updateDataLoader: optional, to keep your translations up to date from a remote server, this is the callback you need. It should returned the ARB content for a given locale and flavor or null if no update is needed.
  • onTranslationsUpdated: optional, once your translations have been updated remotely by updateDataLoader this callback is called for your to rebuild your widget and see the changes.
  • supportedLocales: supported locales for your app.
  • translationsBuilder: builder to provide your custom class containing your sentences.

Flavors specificities

Flavor support is optional in this package, but it's here in case you ever need it :)

Flavor ARB files doesn't have to contain all the sentences of your app, if a sentence is not present in the flavor ARB, the default sentence from the locale will be used.

Let say we have this:

default => helloWorld => Hello World flavor1 => helloWorld => World Hello flavor2 =>

If you call myClass.helloWorld with flavor1 you'll get World Hello If you call myClass.helloWorld with flavor2 you'll get Hello World (the default one)

Same goes for locale, if a sentence is not present in the current locale, the default locale sentence is used.

Dart only project

If you are using Dart without flutter, you can still use this package and get all the feature he bring!

First import the dependencies:

dependencies:
  intl:
  jimtl:

dev_dependencies:
  build_runner: ^2.0.3
  jimtl_codegen:

Then you'll do the same custom class and generate the ARB in the same way. But Dart doesn't have and need LocalizationDelegate. But don't worry here is how to setup your translations.

final delegate = IntlDelegate(
    // default locale to use
    defaultLocale: 'en',
    // default flavor to use
    defaultFlavor: 'flavor1',
    // callback to load ARB content for given locale and flavor
    dataLoader: (String locale, String flavor) async {
      if (flavor == 'default') {
        return await File('./lib/arb/main_$locale.arb').readAsString();
      }
      return await File('./lib/arb/main_${flavor}_$locale.arb').readAsString();
    },
);
// load the current locale and optional flavor
await delegate.load('fr', currentFlavor: 'flavor1');

Once load is finished you can use your custom class as you want.

IntlDelegate can be customized with the following parameters:

  • defaultLocale: default locale to use for your app.
  • defaultFlavor: optional default flavor to use for your app, default to 'default'.
  • currentFlavor: optional current flavor to use for your app, default to 'default'.
  • dataLoader: callback to get the ARB files for a given locale and flavor, this is call by jimtl to let you provide the ARB files when needed.
  • updateDataLoader: optional, to keep your translations up to date from a remote server, this is the callback you need. It should returned the ARB content for a given locale and flavor or null if no update is needed.
  • supportedLocales: supported locales for your app.

Generate Dart code from ARB instead of using IntlDelegate

You might want to have the same as intl_translation provide, meaning having the ARB files generated as dart code.

This is possible with this package, but for that, instead of using the @GenerateArb annotation you'll have to use GenerateIntl

GenerateIntl will first generate the ARB file from the dart code, then it will generate for each locale and flavor some dart files.

Once everything is generated you can setup those generated translations like this:

Intl.defaultLocale = 'en';
await initializeMessages(Intl.defaultLocale!, 'flavor');

After that use your custom class as you wish.

You have the possibility to customize GenerateIntl annotation with the following fields:

  • baseFileName: specify base file name for generated files, default to the name of the annotated class
  • arbDir: dir to generate the ARB file, relative to the current file, default to '.'
  • genDir: dir to generate the dart files, relative to the current file, default to '.'
  • defaultLocale: locale your working with, default to 'en'
  • defaultFlavor: default flavor of your project, default to 'default'
  • locales: List of supported locales of your project
  • flavors: List of flavors of your project
  • codegenMode: mode to pass to underlaying intl_generator, default to 'release'
  • useDeferredLoading: either to use deferred loading for localization files, default to true
  • arbSuppressMetaData: suppress meta data when generating the ARB file, default to false
  • arbSuppressLastModified: suppress last modified when generating the ARB file, default to false
  • arbIncludeSourceText: whether to include source_text in messages, default to false

Limitations

As this package is based on intl, it inherits some of his limitations, the main one is that intl's use a global internal map to find translations in the correct locale.

This means that if you have multiple ARB and custom dart classes you have to be careful that keys are unique across all your apps or one will be overridden by the other.

Another limitation is that Intl has no way to tell us which keys are corresponding to which arguments, to fix this we're using the meta data on the source (generally en) ARB file, so be sure meta data are there or you'll have a StateError.

Examples

Take a look at the basic pure Dart example or our Flutter example.

Contribution

Contributions are welcome! Before doing it please create an issue describing the bug or the feature you want to work on.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_jimtl

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_jimtl: ^0.0.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_jimtl/flutter_jimtl.dart'; 

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_jimtl/flutter_jimtl.dart';
import 'package:jimtl_codegen_example/translations.dart';

void main() async {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  // This widget is the root of your application.
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    //print(TranslationsDelegate.supportedLocales);
    final locales = const [Locale('en'), Locale('fr')];
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      supportedLocales: locales,
      localizationsDelegates: [
        DefaultMaterialLocalizations.delegate,
        TranslationsDelegate<Translations>(
          defaultLocale: locales.first,
          currentFlavor: 'flavor1',
          onTranslationsUpdated: () {
            print('TX updated, need rebuild');
            setState(() {});
          },
          supportedLocales: locales,
          dataLoader: (locale, flavor) async {
            print('local load $locale and $flavor');
            if (flavor == IntlDelegate.defaultFlavorName) {
              return await rootBundle.loadString('assets/arb/translations_$locale.arb');
            }
            return await rootBundle.loadString('assets/arb/translations_${flavor}_$locale.arb');
          },
          updateDataLoader: (locale, flavor) async {
            print('Remote load $locale and $flavor');
            if (locale == 'en' && flavor == 'flavor1') {
              await Future.delayed(Duration(seconds: 10)); //simulate some slow network response
              return await rootBundle.loadString('assets/arb/translations_remote_$locale.arb');
            }
            return null; // no update
          },
          defaultFlavor: IntlDelegate.defaultFlavorName,
          translationsBuilder: () => Translations(),
        ),
      ],
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  Translations getTranslations(BuildContext context) {
    return Translations(); //Translations.of(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(getTranslations(context).counter),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              getTranslations(context).counterPushed(_counter),
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: getTranslations(context).increment,
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
} 

Download Details:

Author: jaumard

Source Code: https://github.com/jaumard/jimtl

#flutter #dart #localization 

Intl Utilities to Easily Manage Localization In Dart and Flutter

Statically-typed Localization Keys Generator for Flutter Translate

flutter_translate_gen

Statically-typed localization keys generator for flutter translate

Please check this wiki page for documentation on how to generate static localization keys.

And here you can find a fully working example.

Issues

Please file any issues, bugs or feature request here.

License

This project is licensed under the MIT License

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add flutter_translate_gen

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  flutter_translate_gen: ^2.2.0

Alternatively, your editor might support dart pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_translate_gen/flutter_translate_gen.dart'; 

example/lib/main.dart

main() {} 

Download Details:

Author: Jesway

Source Code: https://github.com/Jesway/Flutter-Translate-Gen

#flutter #localization 

Statically-typed Localization Keys Generator for Flutter Translate

A Localization formatter Based on The I18next Standard For Dart

i18next

This is an adaptation of i18next standard for Dart with support for Flutter localization techniques. This package is still a work in progress. Mind that this is still a pre-1.0.0 so breaking changes may occur frequently.

  • [x] Support for variables
  • [x] Support for namespaces
  • [x] Support for context
  • [x] Support for simple plural forms (one or plural)
  • [x] Support for multiple plural forms (one, few, many, plural, ...)
  • [x] Plural and context fallbacks
  • [ ] Locale and namespace fallbacks
  • [x] Get string or object tree
  • [x] Support for nesting
  • [ ] Sprintf support
  • [x] Flutter's LocalizationsDelegate support
  • [x] Asset bundle localizations data source (retrieves from pubspec.yaml). See the example for more details.
  • [ ] Resource caching :wip:
  • [ ] Retrieve resource files from server :wip:
  • [ ] Custom post processing

Usage

Simply declare the package in your pubspec.yaml

dependencies:
  i18next: ^0.0.1

To use it with flutter's LocalizationsDelegate you first create I18NextLocalizationDelegate and register it in your WidgetsApp (MaterialApp or CupertinoApp).

I18NextLocalizationDelegate(
  locales: widget.locales,
  // this data source is from where the delegate will retrieve the localizations from (namespaces Map)
  dataSource: ...,
  // optional extra options can be added here
  options: I18NextOptions(...),
),

Then to access and use it, simply call

Widget build(BuildContext context) {
  // It finds the i18next instance on the widgets tree via `Localizations.of`
  I18Next.of(context).t(...);
  ...
}

But if you want to handle it yourself, then simply instantiate it:

I18Next(
  locale,
  // This store is from where i18next will attempt to retrieve the localizations from.
  resourceStore: ...,
  // Optional extra options can be added here
  options: I18NextOptions(...)
);

Syntax

For the simple and straightforward usages:

{
  "key": "Hello World!",
  "nested": {
    "key": "My nested key"
  }
}
i18next.t('key'); // 'Hello World!'
i18next.t('nested.key'); // 'My nested key'

// unmapped keys usually return themselves (when graceful fallback fails)
i18next.t('unspecifiedKey'); // 'unspecifiedKey'
{
  "key": "Hello {{name}}!",
  "grouped_key": "Hello {{grouped.name}}"
}
i18next.t('key', arguments: {'name': 'World'}); // 'Hello World!'
i18next.t('grouped_key', arguments: {'grouped': {'name': 'Grouped World'}}); // 'Hello Grouped World!'
{
  "nesting1": "1 $t(nesting2)",
  "nesting2": "2 $t(nesting3)",
  "nesting3": "3"
}
i18next.t('nesting1'); // "1 2 3"
{
  "key": "item",
  "key_plural": "items",
  "keyWithCount": "{{count}} item",
  "keyWithCount_plural": "{{count}} items"
}
i18next.t('key', count: 0); // 'items'
i18next.t('key', count: 1); // 'item'
i18next.t('key', count: 5); // 'items'
i18next.t('keyWithCount', count: 0); // '0 items'
i18next.t('keyWithCount', count: 1); // '1 item'
i18next.t('keyWithCount', count: 5); // '5 items'

There are also ways of dealing with locales with multiple plural: zero, one, few, many, others (key identifier) (Unsupported)

  • Contexts like gender, are marked via underscores
{
    "genderMessage": "They",
    "genderMessage_male": "Him",
    "genderMessage_female": "Her"
}
i18next.t('genderMessage'); // 'They'
i18next.t('genderMessage', context: 'male'); // 'Him'
i18next.t('genderMessage', context: 'female'); // 'Her'

And can be used with plurals

{
  "friend": "A friend",
  "friend_plural": "{{count}} friends",
  "friend_male": "A boyfriend",
  "friend_female": "A girlfriend",
  "friend_male_plural": "{{count}} boyfriends",
  "friend_female_plural": "{{count}} girlfriends"
}
i18next.t('friend'); // 'A friend'
i18next.t('friend', count: 1); // 'A friend'
i18next.t('friend', count: 100); // '100 friends'

i18next.t('friend', context: 'male', count: 1); // 'A boyfriend'
i18next.t('friend', context: 'female', count: 1); // 'A girlfriend'
i18next.t('friend', context: 'male', count: 100); // '100 boyfriends'
i18next.t('friend', context: 'female', count: 100); // '100 girlfriends'
{
  "key1": "The current date is {{now, MM/DD/YYYY}}",
  "key2": "{{text, uppercase}} just uppercased"
}
i18next.t('key1', arguments: { 'now': DateTime.now() }); // 'The current date is 01/01/2020'
i18next.t('key2', arguments: { 'text': 'my text' }); // 'MY TEXT just uppercased'

There are other usages and possibilities as well, this is just an example of what is defined by this format.

Namespaces: A namespace can be thought of as logical groupings of different sets of translations. In a given namespace you could have a set of languages, each with their own set of keys. They can also be understood as separate files. For example:

  • common.json: Things that are reused everywhere, eg. Button labels 'save', 'cancel'
  • validation.json: All validation texts
  • glossary.json: Words we want to be reused consistently inside texts
// common.json
{
  "myKey": "This key is in common"
}

// feature.json
{
  "myKey": "This key is in my feature"
}
i18next.t('common:myKey'); // 'This key is in common'
i18next.t('feature:myKey'); // 'This key is in my feature'
  • Context/plural fallback mechanism:
{
  "friend": "A friend",
  "friend_female": "A girlfriend"
}
i18next.t('friend'); // 'A friend'

i18next.t('friend', count: 1); // 'A friend'
// It fallbacks to `friend` since `friend_plural` is not present
i18next.t('friend', count: 2); // 'A friend'

i18next.t('friend', context: 'female'); // 'A girlfriend'
// It fallbacks to `friend` since `friend_male` is not present
i18next.t('friend', context: 'male'); // 'A friend'

There is a way to also set the default namespace or a order of namespaces so a key knows where to start looking for the translation.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add i18next

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  i18next: ^0.5.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:i18next/i18next.dart'; 

example/lib/main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:i18next/i18next.dart';
import 'package:intl/intl.dart';

import 'localizations.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  final List<Locale> locales = const [
    Locale('en', 'US'),
    Locale('pt', 'BR'),
    // TODO: add multi plural language(s)
  ];

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Locale locale;

  @override
  void initState() {
    super.initState();

    locale = widget.locales.first;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'I18nu Demo',
      theme: ThemeData(
        dividerTheme: const DividerThemeData(
          color: Colors.black45,
          space: 32.0,
        ),
      ),
      localizationsDelegates: [
        ...GlobalMaterialLocalizations.delegates,
        I18NextLocalizationDelegate(
          locales: widget.locales,
          dataSource: AssetBundleLocalizationDataSource(
            // This is the path for the files declared in pubspec which should
            // contain all of your localizations
            bundlePath: 'localizations',
          ),
          // extra formatting options can be added here
          options: const I18NextOptions(formatter: formatter),
        ),
      ],
      home: MyHomePage(
        supportedLocales: widget.locales,
        onUpdateLocale: updateLocale,
      ),
      locale: locale,
      supportedLocales: widget.locales,
    );
  }

  void updateLocale(Locale newLocale) {
    setState(() {
      locale = newLocale;
    });
  }

  static String formatter(Object value, String? format, Locale? locale) {
    switch (format) {
      case 'uppercase':
        return value.toString().toUpperCase();
      case 'lowercase':
        return value.toString().toLowerCase();
      default:
        if (value is DateTime) {
          return DateFormat(format, locale?.toString()).format(value);
        }
    }
    return value.toString();
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    Key? key,
    required this.supportedLocales,
    required this.onUpdateLocale,
  }) : super(key: key);

  final List<Locale> supportedLocales;
  final ValueChanged<Locale> onUpdateLocale;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  String _gender = '';

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final homepageL10n = HomePageL10n.of(context);
    final counterL10n = CounterL10n.of(context);

    return Scaffold(
      appBar: AppBar(title: Text(homepageL10n.title)),
      body: SingleChildScrollView(
        padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            CupertinoSegmentedControl<Locale>(
              children: {
                for (var e in widget.supportedLocales) e: Text(e.toString())
              },
              groupValue: Localizations.localeOf(context),
              onValueChanged: widget.onUpdateLocale,
            ),
            const Divider(),
            Text(
              homepageL10n.hello(name: 'Name', world: 'Flutter'),
              style: theme.textTheme.headline6,
            ),
            Text(
              homepageL10n.today(DateTime.now()),
              style: theme.textTheme.subtitle2,
            ),
            CupertinoSegmentedControl<String>(
              padding: const EdgeInsets.symmetric(vertical: 8),
              children: const {
                'male': Text('MALE'),
                'female': Text('FEMALE'),
                '': Text('OTHER'),
              },
              groupValue: _gender,
              onValueChanged: updateGender,
            ),
            Text(homepageL10n.gendered(_gender)),
            const Divider(),
            Text(
              counterL10n.clicked(_counter),
              style: theme.textTheme.headline4,
            ),
            TextButton(
              onPressed: resetCounter,
              child: Text(counterL10n.resetCounter),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter,
        tooltip: counterL10n.clickMe,
        child: const Icon(Icons.add),
      ),
    );
  }

  void incrementCounter() => setState(() => _counter++);

  void resetCounter() => setState(() => _counter = 0);

  void updateGender(String gender) => setState(() => _gender = gender);
} 

Download Details:

Author: nubank

Source Code: https://github.com/nubank/i18next

#dart #I18next #localization 

A Localization formatter Based on The I18next Standard For Dart
Royce  Reinger

Royce Reinger

1658999880

Hiatus: Localization QA tool

hiatus

hiatus is a localization QA tool. Reads various bilingual file formats, runs checks and reports errors detected.

Check Items

Glossary
When a glossary source term found in a source segment, checks if corresponding glossary target term exists in a target segment. RegExp supported.

Search Source or Target Text (Defined as monolingual)
Loads expressions from the list, and report errors if the expressions found in a segment. You can choose which segment to search (source or target). RegExp supported.

Inconsistency
Checks inconsistencies in two ways - Source to Target & Target to Source

Numbers
Detects numbers in source but NOT in target.

TTX, XLZ, SDLXLIFF Tag Check
Detects missing or added internal tags.

Length
Length of source and target segments are different more/less than +/- 50%

Skipped Translation
Reports errors if a target segment is blank.

Identical Translation
Reports errors if source and target segments are same

Alphanumeric Strings in Target but NOT in Source (Defined as unsourced)
Effective only when target is non-Alphabet language (i.e. Japanese, Chinese, Korean...).

Alphanumeric Strings in Source but NOT in Target (Defined as unsourced_rev)
Effective only when source is non-Alphabet language (i.e. Japanese, Chinese, Korean...).

Software
Checks if 1) Hotkeys (i.e. &A, _A), 2) Missing/Added variables (i.e. %s, %d), and 3) '...' at the end (i.e. Save As...) are consistent between source and target segments.

Spell
Spell check using GNU Aspell library.

Supported Bilingual File Formats

  • XLZ (Idiom)
  • TTX
  • TMX
  • TXT (tab-separated file)
  • CSV (LocStudio dump by CSVDump add-in)
  • XLS/XLSX (By default, read as column A = Source, column B = Target, column C = Comment)
  • RTF/DOC/DOCX (Trados format bilingual)
  • TBX
  • SDLXLIFF

Features

  • hiatus can automatically convert dictionary form into possible active forms for English (Optional).
    Example: Converts write into RegExp (?:write|writes|writing|wrote|written).
  • Auto-detect encoding with chardet2 library to prevent garbled character issues.
  • Simple output report (XLS). Easy to filter.
  • Can suppress known false errors by specifying Ignore List.
  • Source code is published here. You can modify as you like if you want.

Precautions

  • Do NOT copy anything while hiatus is running.
    hiatus uses clipboard while reading XLSX/DOC files (including reading XLS Ignore list).
    When you use these functions, leave clipboard during execution. Do not perform any copy operations.
  • Ignore list does not work correctly in some cases (See "About Ignore List" for details)

Environment

Ruby 1.9.2, 1.9.3 or 2.0.0
Windows XP, Windows 7
*hiatus is tested only in JA and EN environment. However, it might work correctly on other languages as chardet2 library is implemented to support various encodings.

Installation

  1. Install Ruby 2.0.0. Check on tk option on installation
  2. Install GNU Aspell (Mac, Win) and dictionaries you need.
  3. Add 'C:\Program Files (x86)\Aspell\bin' to your environmental variable PATH.
  4. On 'C:\Program Files (x86)\Aspell\bin', copy aspell-15.dll and save it as aspell.dll. Also save pspell-15.dll as pspell.dll.
  5. Start command prompt and run following commands
    gem install nokogiri
    gem install zip
    gem install ffi
    gem install ffi-aspell
    gem install chardet2

How to use hiatus?

Fill in necessary fields on config.yaml, and run hiatus.rb.
Then error report will be generated.

About config.yaml

 required:  
   bilingual: Folder path of the target bilingual files (including subfolders)  
   output: Folder path of the output report  
   report: Format of the output report (Currently, only xls)  
   source: Source Language  
   target: Target Language  
   glossary: Folder path of the Glossary files (including subfolders)
   monolingual: Folder path of the Monolingual files (including subfolders) 

check: Choose true or false for each check.
   glossary: true  
   inconsistency_s2t: true  
   inconsistency_t2s: true  
   missingtag: true  
   skip: true  
   identical: false   
   monolingual: true  
   numbers: true  
   unsourced: true  
   unsourced_rev: true   
   length: false 
   software: true 
   spell: true 

 option:  
   filter_by: For XLZ - Only when the "Note" value is same as this value, the entry will be checked. Other entries will be skipped.   
   ignore100: true/false. For TTX/XLZ/SDLXLIFF, when true, 100% match will be skipped.  
   ignoreICE: true/false. For XLZ/SDLXLIFF, when true, ICE match will be skipped.  
   ignorelist: Path to the ignore list (XLSX file)

About Ignore List

You can skip known false errors by specifying ignore list.
Open the hiatus report (XLSX file) and mark ignore in "Fixed?" column (column M), and save it as XML spreadsheet 2003 format.
Then specify the full path of the XML file in the ignoreList field. Use semicolon to specify multiple files.
For example:

   ignorelist: Y:\Sample_files\130412_report.xml  
   ignorelist: Y:\Sample_files\130412_report.xml;Y:\Sample_files\130522_report.xml  

Then, marked errors will not reported next time.

Note:
You can specify XLSX (or CSV file) in ignoreList field, however, it is not recommended as reading XLSX file is unstable. XML file is recommended.

How to create Glossary file?

See the following instructions and the sample files in !Sample_files folder.

Glossary File Format

Four-Column TAB delimited Text
UTF-8 without BOM is recommended (Encoding is automatically detected by chardet)

Structure

Column 1Column 2Column 3Column 4
SourceTargetOptionComment
ColumnDescription
SourceGlossary source term. RegExp supported. Required
TargetGlossary target term. RegExp supported. Required
OptionConversion option. Required
CommentComment. Optional

About Options

Available options are combination of followings

OptionDescription
iignore case + Auto Conversion
mmultiline + Auto Conversion
eextended + Auto Conversion
zNo Conversion + No RegExp + Case-Insensitive
BlankNo Conversion + No RegExp + Case-Sensitive (= As is)
  
Prefix # 
#<X>Auto Conversion OFF. When you use your own RegExp, add # at the beginning of the option field

Sample

Server	 サーバー	z
(?:node|nodes)	ノード	#i	ノードの訳に注意
import(?:ing)	インポート	#i
Japan	日本		JapanはCase-sensitive
run	走る	i	
(?<!start¥-|end¥-)point	点	#i	Feedback No.2

You can try Ruby RegExp on rubular.
RegExp is based on onigumo, see Ruby 2.0.0 reference for details of RegExp available in Ruby 2.0.0.

How to create Monolingual file?

See below and the sample files in !Sample_files folder.

Monolingual File Format

Four-Column TAB delimited Text
UTF-8 without BOM is recommended (Encoding is automatically detected by chardet)

Structure

Column 1Column 2Column 3Column 4
s or tExpressionOptionComment
ColumnDescription
s or tSegment to search. 's' is source, 't' is target segment. Required
ExpressionSearch expression. RegExp supported. Required
OptionConversion option. Required
CommentComment. Optional

About Option

Available options are combination of followings

OptionDescription
iignore case + Auto Conversion
mmultiline + Auto Conversion
eextended + Auto Conversion
zNo Conversion + No RegExp + Case-Insensitive
BlankNo Conversion + No RegExp + Case-Sensitive (= As is)
  
Prefix # 
#<X>Auto Conversion OFF. When you use your own RegExp, add # at the beginning of the option field

Sample

t	;	#	全角セミコロン;を使用しない
t	[\p{Katakana}ー]・	#	カタカナ間の中黒を使用しない
t	[0123456789]+	#	全角数字を禁止
s	not	z	否定文?
t	Shared Document	#i	Windows のファイル パスはローカライズする(共有ドキュメント)。
t	[あいうえお]	#	Hiragana left

You can try Ruby RegExp on rubular.
RegExp is based on onigumo, see Ruby 2.0.0 reference for details of RegExp available in Ruby 2.0.0.


For more details, please see
Slide: http://www.slideshare.net/ahanba/how-to-use-hiatus
Demo: http://youtu.be/6yaiI0OS-3c


Author: Ahanba
Source Code: https://github.com/ahanba/hiatus 
License: GPL License

#ruby #localization #tool 

Hiatus: Localization QA tool