Translate

Wednesday, October 31, 2018

5 Things Every Developer Should Know About Localization (l10n)

Source: https://dzone.com/articles/5-things-every-developer-should-know-about-localiz


Developers should constantly be seeking to learn new skills, and one of those is localization. Learn more about what localization is and does here.


Being a developer you understand that to grow you need to be always learning, whether from books, visiting conferences, or reading articles like this one. Among the new essential skills, you should be acquiring right now is rightfully placed the ability to build software that supports localization.
Localization is basically adapting software to be easily consumed in different languages and locales. Any person around the world, speaking any language could start using your app, making orders from your website, or playing the game you developed in their native language. Just imagine how many more customers your software would get. Expanding target markets for your product is a big deal and if you could make that happen you’d be more valuable as a developer.
In this article, I will tell you what exactly you should know about developing software that could support multiple languages and locales with no translations needed (unless you wish to). After discussing things with my fellow developers at Crowdin, I wish somebody told them about localization sooner, so I’ve come up with 5 tips about localization you should also know. Keep on reading if you want to extend your resume with a new skill that you can learn right now – building software ready for localization.

1. Add Basic Support for Localization Right from The Start

Most likely, support for only 1 language will not be enough at some point. So you better be prepared for what is to come. Especially if there’s already a plan of taking the product globally – be sure to discuss that with the project manager or the customer directly before you even start writing the code. If you don’t prepare from the start, you’ll have to spend extra time refactoring all the code to make it support localization later. So better do your initial prep work for localization now.
Of course, there are 360 million native English speakers and one of the half a billion people who speak English as a second language. But what about people who don’t speak English? There are almost 1.2 billion Chinese native speakers, about 400 million Spanish speakers, and many more people speaking other world languages according to Babble-on. So, supporting several languages is not something extra anymore, it’s something people expect from your product.

2. Externalize All the Localization Resources

You wouldn’t want to be looking for the text strings and retrieving them from the code manually to pass them to translators. Instead, think of some faster and easier solution like localization text wrappers or using keys as an alternative to the hardcoded texts.
For example, in the Android development you can use Resources::getString and format your strings in the following way:
<string name="string.hello">Hello %s</string>
res/values/strings.xml

<string name="string.hello">%s привіт</string>
res/values-ua/strings.xml

In this case, you’ll receive:
String username = "Jane";
String result = getString(R.string.hello, username);
// EN result "Hello Jane"
// UK result  "Jane привіт"
The advised solution here is to use unique string keys and store the actual text strings in a separate file for each language. Create a separate file or directory for each locale your product is going to support. For every file localized, place the translated version to exactly the same path relative to the root and give the same name as original file plus the locale identifier.
For example, Android uses files with the “.xml” file extension to keep and fetch all the strings used within the app, for each supported language. A simple method call in your code will lookup and return the requested string based on the current language in use on the device.
You’ll have to place all the texts in the default language you’re using, let’s assume it’s English, intores/values-en/strings.xml.
Then you’ll be able to specify several res/<qualifiers>/ directories with different qualifiers. Each for a corresponding language and locale. This way, to get the French version of the file your app will look in res/values-fr/strings.xml, and for the Spanish version of the file, it will look in res/values-es/strings.xml.

3. Add Localization Comments

Translators who will later have to work with the strings file you provided, usually do not get much additional context with the file. So adding descriptive comments in some cases might help them a lot.
Consider adding comments in the following cases:
  • The string might come out ambiguous. For example “Bookmark” might be both a noun and a verb, and that’s something difficult to guess without context. In this case, a descriptive string key also helps, for example instead of:
<string name="create">Create</string>

it’s better to use
<string name="actionbutton.create">Create</string>

  • If the string contains a variable – add an example or a short explanation of what might it be.
You should also consider that every platform has an established format for localization comments, so make sure you research and follow it. As there are a lot of automated tools like Crowdin, that might later parse these comments for easier access and use by translators.

4. Use Localization Libraries to Simplify the Process

Each language has its own specifics for the text layout, formatting, and more. That’s the work you don’t have to do manually as it’s easily automated. It’s highly recommended to use localization libraries (for example, you can use International Components for Unicode (ICU)) that will help you with handling the following aspects:
  • support for right-to-left (RTL) and left-to-right (LTR) scripts in the same string
  • applying numeric, currency, date and time format strings. For example, the date format changes based on the language, for English it’s 7/23/2018 (en-US) and for French, it’s23/07/2018 (fr-FR)
  • Usage of plurals. Examples:
{ count, plural,
   =0 {No candy left}
   one {Got # candy left}
other {Got # candies left}

{
gender, select,
   female {{
      count, plural,
         =0 {Ela não capturou nenhum}
         one {Ela tem capturado um só}
         other {Ela tem capturado #}
     }}
   other {{
      count, plural,
         =0 {Ele não capturou nenhum}
         one {Ele tem capturado um só}
         other {Ele tem capturado #}
     }}
}

5. Set up An Integration with A Localization Management Platform

Once your customer is ready to localize the software you’ve built, you’ll need to export all the localizable text into some common file format that will be passed to the team of translators. Some companies still do localization with Excel files. But let me stop you here, don’t do localization in Excel. That’s an amazing tool, but for other great things. Just imagine for a second how making sure that all the strings in the .xls or .xlsx file are up-to-date and all the translations are in sync with your code would look like. What if your product is updated monthly/weekly/daily? Trust me, you wouldn’t like dealing with that manually.
That’s why most companies automate all these mundane text strings exchanges with integrations or in-house tools. For example, you can set up API or Git integration with a localization management platform. Once you set up the integration, our tool will get the localization files from your repo and upload them to the Editor, where strings will look user-friendly with the comments you provided. Once the translations are made the system combines them into a file and syncs them with your code as a merge request or as a file that can be added to the directory for a specific locale.

Conclusion

Software localization is a great way to extend your product’s target markets as people are more likely to use the software in their native language. Localization is also something that is never done overnight, so hopefully, now you’ll be prepared as you already know the basics.
Originally published at Crowdin Blog.

Tuesday, October 23, 2018

Jenkins in a Nutshell


by
Source: https://dzone.com/articles/jenkins-in-a-nutshell
Learn about Jenkins for continuous integration as part of CI/CD, its advantages, and alternatives.

In many projects, the product development workflow has three main concerns: building, testing, and deployment. Each change to the code means something could accidentally go wrong, so in order to prevent this from happening developers adopt many strategies to diminish incidents and bugs. Jenkins, and other continuous integration (CI) tools are used together with a source version software (such as GIT) to test and quickly evaluate the updated code.
In this article, we will talk about Jenkins, applicable scenarios, and alternatives to automated testing, deployment, and delivering solutions.

About Jenkins

Jenkins is a popular self-contained, open-source automation server to perform continuous integration and build automation. Its elementary functionality is executing a predefined list of phases or jobs. In other words, every change in a repository triggers a pipeline of jobs that evaluates and executes different tasks to accomplish what has been previously defined.
Each phase is monitored and allows you to stop the entire process and the change will be reported to the user by Jenkins. In large companies, it is common for multiple teams to work on the same project without knowing what the other teams are doing on the same code base. Those changes can create bugs that will only be revealed when both codes are integrated into the same branch. Since Jenkins can run its predefined jobs for every commit, it will be able to detect and notify developers that something is not right and where it is.
More on the subject:
Thousands of add-ons can be integrated with Jenkins, they provide support for different types of build, version control systems, automation, and more. It can be installed through native system packages, Docker, or be run by any machine with a Java environment installed.
Jenkins is often used for building projects; running tests to spot bugs, to analyze static code, and deployment. It also executes repetitive tasks, saves time, and optimizes developing processes.
Beginning with the second version, Jenkins introduced Pipelines, a different way to programmatically define a project build workflow. Before pipelines, the CI description was defined and stored outside the repository—it was designed to evaluate—now, with Pipelines, CI files are present in project source code. The file describes the workflow through a language which can be used to create different jobs in sequence or in parallel.
Below is an example of a pipeline with four jobs (stages) which facilitates debugging when one of them fails (from https://jenkins.io/doc/pipeline/examples/).
jenkins

Jenkins Use Cases

Let’s take a look at some of the main scenarios Jenkins plays a critical part in.

Continuous Integration (CI)

Continuous integration is a practice that forces developers to frequently integrate their code into a central repository. Instead of building out new features to the end without any quality measurement, every change is tested against the central repository in order to anticipate errors.
Every developer commits daily to a shared mainline and every commit triggers an automated process to build and test. If building or testing fails it can be detected and fixed within minutes without compromising the whole structure, workflow, and project. In that way, it is possible to isolate problems, solving them faster and provide higher-quality products.

Continuous Delivery (CD)

Continuous delivery is the ability to make changes of all types—such as new features, configuration changes, error fixes, experiments—into production in a safe and efficient manner using short work cycles.
The main goal in continuous delivery is to make deployments predictable as routine activities that can be achieved upon request. To be successful, the code needs to always be in a deployable state even when there is a scenario with lots of developers working and making changes on a daily basis. All of the code progress and changes are delivered in a nonstop way with high quality and low risks. The end result is one or more artifacts that can be deployed to production.

Continuous Deployment (CD)

Continuous deployment, also known as continuous implementation, is an advanced stage of continuous delivery that the automation process does not end at the delivery stage. In this methodology, every change that is validated at the automatic testing stage is later implemented at the production stage.
The fail fast strategy is always of the utmost importance when deploying to production. Since every change is deployed to production, it is possible to identify edge cases and unexpected behaviors that would be very hard to identify with automated tests. To fully take advantage of continuous deployment, it is important to have solid logging technology that allows you to identify the increasing error count on newer versions. In addition, a trustworthy orchestration technology like Kubernetes that will allow the new version to slowly be deployed to users until the full rollout or an incident is detected and the version is canceled.

Automation

As a job executor, Jenkins can be used to automate repetitive tasks like backup/restore databases, turn on or turn off machines, collect statistics about a service and other tasks. Since every job can be scheduled, repetitive tasks can have a desired time interval (like once a day, once a week, every fifth day of the month, and so forth).

Jenkins Alternatives

Although Jenkins is a good option for an automated, CI/CD server, there are other options on the market such as Gitlab CI/CD, Circle CI, Travis or Bamboo.

GitLab CI/CD

GitLab is a full-featured software development platform that includes a module called GitLab CI/CD to leverage the ability to build, test, and deploy without external requirements (such as Jenkins). It is a single application that can be used in all stages of the developers’ work cycle on the same project: product, development, QA, security, and operations.
GitLab is a solution that enables teams to cooperate and work from a single step instead of managing thousands of threads across disparate tools. It provides a single data store, one user interface, and one permission model across the developers’ life cycle. This permits teams to collaborate reducing cycle time and focusing on building software more quickly and efficiently.
Though Gitlab covers the CI/CD cycle thoroughly, it fails to do so for automation tasks since it does not have scheduling options. It can be a very good alternative since it integrates source code versioning and CI into the same tool.
Gitlab comes in a variety of flavors: there is a community, open-source edition that can be deployed locally, and some paid versions with an increasing number of features.

Circle CI

Circle CI is a hosted continuous integration server. After Circle CI is authorized on GitHub or Bitbucket, every code change triggers tests in a clean container or VM. After this, an email is sent every time there is a successful test completed or a failure. Any project with a reporting library provides code test coverage results. Circle CI is simple to configure, has a comprehensive web interface, and can be integrated with multiple source code versioning tools.

Bamboo CI

Bamboo is a solution for continuous integration, deployment, and delivery. Bamboo allows you to create a multi-stage build plan, set up triggers upon commits, and assign agents to builds and deployments. It also allows you to run automated tests in every code change which makes catching bugs easier and faster. Bamboo supports continuous deliveries as well.
Bamboo’s brightest feature is its seamless integration with Atlassian products: Jira Software, Bitbucket, and Fisheye, and can be improved with hundreds of add-ons that are available at Atlassian marketplace.

Travis CI

Travis is another open-source solution that also offers a free hosted option for open-source projects (paid for enterprise clients). It uses a solution similar to Jenkins Pipelines: you add a file called .travis.yml that describes the project’s own build workflow. It also has parallel jobs builds but it does not have the same size of add-ons available for Jenkins.

Endnotes

Integration solutions are a key step towards reaching delivery reliability. Every developer commits daily to a shared mainline and every commit triggers an automated workflow for building and testing; if building and testing fail it is possible to repair what is wrong quickly and safely and thereby increase productivity in the workflow. When we have a way to find problems and solve them quickly, we release higher-quality products and more stable experiences to the client.
There are lots of options on the market to choose from to help the developers’ workflow. As outlined above, some of these are free solutions and open-source, while others are paid. Jenkins is one of the oldest open-source tools out there and as such also extremely popular. We at Logz.io are no exception here, and we use Jenkins to run tests, create Docker containers, build code, and push to staging and production.

Friday, October 19, 2018

Implementing a Sliding Window Stream/Spliterator in Java

Source: https://dzone.com/articles/implementing-a-sliding-window-streamspliterator-in

Let's go through this tutorial on creating a sliding window in Java using streams and spliterators! Trust me — you'll be happy you did.

In this article, we'll take a look at how to implement a custom sliding window Stream/Spliterator in Java. Does the world need another way of implementing a sliding window operation in Java? Probably not, but you do — for your self-development.

Sliding Window

Simply put, the Sliding Window algorithm is a method of traversing data structures by moving a fixed-size window (sublist) over a sequence in fixed steps.
It gets much more intuitive when shown in an example.
If we wanted to traverse a list [1 2 3 4 5] by using the window of the size 3, we'd be merely looking at the following groups:
  1. [1 2 3]
  2. [2 3 4]
  3. [3 4 5]
But, if we wanted to traverse the same list using a window that's bigger than the collection's size, we wouldn't get a single element.

Implementation

To be able to create a custom Stream, we need to implement a custom Spliterator.
In our case, we need to be able to iterate over groups represented by Stream<T> sequences, so we need to implement the Spliterator interface and specify the generic type parameter:
public class SlidingWindowSpliterator<T> implements Spliterator<Stream<T>> {
// ...
}

Then, it turns out we have a bunch of methods to implement:
public class SlidingWindowSpliterator<T> implements Spliterator<Stream<T>> {
    @Override
    public boolean tryAdvance(Consumer<? super Stream<T>> action) {
        return false;
    }
    @Override
    public Spliterator<Stream<T>> trySplit() {
        return null;
    }
    @Override
    public long estimateSize() {
        return 0;
    }
    @Override
    public int characteristics() {
        return 0;
    }
}

We'll also need a few fields for storing buffered elements, the window size parameter, an iterator of the source collection, and a precomputed size estimation (we'll need that later on):
private final Queue<T> buffer;
private final Iterator<T> sourceIterator;
private final int windowSize;
private final int size;

Before we can start implementing interface methods, we need to have an ability to instantiate our tool.
In this case, we'll restrict the visibility of the constructor and expose a public static factory method instead:
private SlidingWindowSpliterator(Collection<T> source, int windowSize) {
    this.buffer = new ArrayDeque<>(windowSize);
    this.sourceIterator = Objects.requireNonNull(source).iterator();
    this.windowSize = windowSize;
    this.size = calculateSize(source, windowSize);
}
static <T> Stream<Stream<T>> windowed(Collection<T> stream, int windowSize) {
    return StreamSupport.stream(
    new SlidingWindowSpliterator<>(stream, windowSize), false);
}

Now, let's implement the easy part of the Spliterator methods.
In our case, there's no easy way to split the sequence, so when implementingtrySplit(), we default to values specified in the documentation. Luckily, size can be calculated quite easily:
private static int calculateSize(Collection<?> source, int windowSize) {
    return source.size() < windowSize
    ? 0
    : source.size() - windowSize + 1;
}
@Override 
public Spliterator<Stream<T>> trySplit() { 
    return null; 
} 
@Override 
public long estimateSize() { 
    return size; 
}

In  characteristics(), we specify:
  1.  ORDERED  — Because of the encounter, order matters.
  2.  NONNULL  — This because elements will never be null (although can contain nulls).
  3.  SIZED — This is due to the fact that size is predictable.
@Override
public int characteristics() {
    return ORDERED | NONNULL | SIZED;
}

Implementing tryAdvance

And, here comes the crucial part — the method responsible for the actual grouping and iteration.
Firstly, if the window is smaller than 1, then there's nothing to iterate so that we can short-circuit immediately:
@Override
public boolean tryAdvance(Consumer<? super Stream<T>> action) {
    if (windowSize < 1) {
        return false;
    }
    // ...
}

And now, to generate the first sublist, we need to start iterating and filling the buffer:
while (sourceIterator.hasNext()) {
    buffer.add(sourceIterator.next());
    // ...
}

Once the buffer is filled, we can dispatch the complete group and discard the oldest element from the buffer.
Here comes a crucial part — one might be tempted to pass the  buffer.stream()  to the accept()  method, which is a huge mistake. Streams are lazily bound to an underlying collection, which means that if the source changes, the Stream changes as well.
In order to avoid the problem and decouple our groups from the internal buffer representation, we need to snapshot the current state of the buffer before creating each Stream instance. We'll back Stream instances with arrays to make them as lightweight as possible.
Since Java doesn't support generic arrays, we need to do some ugly casting:
if (buffer.size() == windowSize) {
    action.accept(Arrays.stream((T[]) buffer.toArray(new Object[0])));
    buffer.poll();
    return sourceIterator.hasNext();
}

...and, voila, we are ready to use it:
windowed(List.of(1,2,3,4,5), 3)
.map(group -> group.collect(toList()))
.forEach(System.out::println);
// result
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]

For additional practices, you can implement a possibility of specifying a custom step size (now it's implicitly set to 1).

Complete Example

package com.pivovarit.stream;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.Queue;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class SlidingWindowSpliterator<T> implements Spliterator<Stream<T>> {
    static <T> Stream<Stream<T>> windowed(Collection<T> stream, int windowSize) {
        return StreamSupport.stream(
        new SlidingWindowSpliterator<>(stream, windowSize), false);
    }
    private final Queue<T> buffer;
    private final Iterator<T> sourceIterator;
    private final int windowSize;
    private final int size;
    private SlidingWindowSpliterator(Collection<T> source, int windowSize) {
        this.buffer = new ArrayDeque<>(windowSize);
        this.sourceIterator = Objects.requireNonNull(source).iterator();
        this.windowSize = windowSize;
        this.size = calculateSize(source, windowSize);
    }
    @Override
    public boolean tryAdvance(Consumer<? super Stream<T>> action) {
        if (windowSize < 1) {
            return false;
        }
        while (sourceIterator.hasNext()) {
            buffer.add(sourceIterator.next());
            if (buffer.size() == windowSize) {
                action.accept(Arrays.stream((T[]) buffer.toArray(new Object[0])));
                buffer.poll();
                return sourceIterator.hasNext();
            }
        }
        return false;
    }
    @Override
    public Spliterator<Stream<T>> trySplit() {
        return null;
    }
    @Override
    public long estimateSize() {
        return size;
    }
    @Override
    public int characteristics() {
        return ORDERED | NONNULL | SIZED;
    }
    private static int calculateSize(Collection<?> source, int windowSize) {
        return source.size() < windowSize
        ? 0
        : source.size() - windowSize + 1;
    }
}

Source

The complete example can be also found on GitHub.
Have a good idea about how to improve it? Feel free to issue a PR!