To the Multi lang blog - English, 日本語, Tiếng Việt
A multi languages blog
As a foreigner engineer living in Japan, working with multi-platform services and open projects with other engineers all over the World, supporting multi languages is a must-have feature I would like to add to my blog.
The setup is just simple: one line of CSS, some tune-up in font family styling and you are done almost there.
There is one single issue which I encounter many time: does the new typeface look good everywhere, for my languages? Currently, I'm using English, Japanese and Vietnamese almost equally among them. So both of them should be nicely rendered.
As a general mobile developer, I have no favorite font family. My very basic setup will be Roboto or Noto. This blog is current using something I haven't test yet, but if it works fine, I will leave it there.
Now there would be some non-English paragraph to test foreign languages some other languages.
A Japanese Lorem Ipsum
用モニシ市社みねスけ鎖絵アラ費分マク由男4校タア系際ドルゆ料方結美るきラ想転治チマ期相ちイあ者亜ま。同タケ主共クう整3独べまい市画キフミ告稿チ表関ラ政89無ぞし募示ヱクサ権用ルざ十歌みト握68演せさ既新ぎ琶名ンょ問発監す。動信出リどえて定杯レモ当79打ドクる記組ク責明ドば側意ス位牟クヱマ条着レヱ能強な告陸済蘭注ツ線物のぽべた転催野川列ばな。
無ルおねト照徳54玉うフ東質レイヤミ討告ゃン討一ヱフエソ番未チヱヌ帰物ム援宇すょレゆ認被ネヱ取展ぼど見行働迎彫慰て。投ぞじぶど州中とめ松広サシワ新十ゃリげゅ三属ヲタコ簡山ぞろゅ話満ぜる申望4帰ロナタ引受び込松正みすぽ松統お作祭ツ供担作フお検海詳暮点ろ。表外ヨ反辞タ城理さぐに替王ネ更増ユ業71界藤ッぽ年3軽内えス排視ヒトウ首村トホツ算禁在ざ制決次修級ぽゃぞも。
午相カネムサ業向シウロク転予れづり問営ハエラモ代強ラ内分リづ囲必リオ博辞でっ品安変ケ気言えッたと本航レオセ転論ドえ増口コヒセ擬5表労ヒトアレ広画てねーひ連汚ッずつこ。報カ旅応ムハリ事消証ん官堅れくラ月車だゅまど以4分べおむ一在ひちゅい心辞クぜもぱ釜所ト響併室だわ力42千つづほへ日天ふフらり多68箱僕滞よさむて。
A Vietnamese Lorem Ipsum
Lorem Ipsum chỉ đơn giản là một đoạn văn bản giả, được dùng vào việc trình bày và dàn trang phục vụ cho in ấn. Lorem Ipsum đã được sử dụng như một văn bản chuẩn cho ngành công nghiệp in ấn từ những năm 1500, khi một họa sĩ vô danh ghép nhiều đoạn văn bản với nhau để tạo thành một bản mẫu văn bản. Đoạn văn bản này không những đã tồn tại năm thế kỉ, mà khi được áp dụng vào tin học văn phòng, nội dung của nó vẫn không hề bị thay đổi. Nó đã được phổ biến trong những năm 1960 nhờ việc bán những bản giấy Letraset in những đoạn Lorem Ipsum, và gần đây hơn, được sử dụng trong các ứng dụng dàn trang, như Aldus PageMaker.
Trái với quan điểm chung của số đông, Lorem Ipsum không phải chỉ là một đoạn văn bản ngẫu nhiên. Người ta tìm thấy nguồn gốc của nó từ những tác phẩm văn học la-tinh cổ điển xuất hiện từ năm 45 trước Công Nguyên, nghĩa là nó đã có khoảng hơn 2000 tuổi. Một giáo sư của trường Hampden-Sydney College (bang Virginia - Mỹ) quan tâm tới một trong những từ la-tinh khó hiểu nhất, "consectetur", trích từ một đoạn của Lorem Ipsum, và đã nghiên cứu tất cả các ứng dụng của từ này trong văn học cổ điển, để từ đó tìm ra nguồn gốc không thể chối cãi của Lorem Ipsum. Thật ra, nó được tìm thấy trong các đoạn 1.10.32 và 1.10.33 của "De Finibus Bonorum et Malorum" (Đỉnh tối thượng của Cái Tốt và Cái Xấu) viết bởi Cicero vào năm 45 trước Công Nguyên. Cuốn sách này là một luận thuyết đạo lí rất phổ biến trong thời kì Phục Hưng. Dòng đầu tiên của Lorem Ipsum, "Lorem ipsum dolor sit amet..." được trích từ một câu trong đoạn thứ 1.10.32.
Trích đoạn chuẩn của Lorem Ipsum được sử dụng từ thế kỉ thứ 16 và được tái bản sau đó cho những người quan tâm đến nó. Đoạn 1.10.32 và 1.10.33 trong cuốn "De Finibus Bonorum et Malorum" của Cicero cũng được tái bản lại theo đúng cấu trúc gốc, kèm theo phiên bản tiếng Anh được dịch bởi H. Rackham vào năm 1914.
Last but not least: Programming languages
Talking to languages, with an engineer, there should not be forgotten about Programming languages. I would like to have beautiful code block for some of my most familiar Programming languages. Let's try some.
1. Java
package com.example.app;
import android.app.Application;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
AndroidThreeTen.init(this); // init the lib here
}
}
2. XML
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2016 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="360dp"
android:height="200dp"
android:viewportWidth="360"
android:viewportHeight="200">
<path
android:name="hash"
android:pathData="M39,45L39,80 M57,45L57,80 M66,54L31,54 M66,71L31,71"
android:strokeColor="@color/indigo_400"
android:strokeLineCap="round"
android:strokeWidth="@dimen/hashtag_stroke_width" />
<path
android:name="i_body"
android:pathData="M83,82L107,82A2,2 0,0 1,109 84L109,155A2,2 0,0 1,107 157L83,157A2,2 0,0 1,81 155L81,84A2,2 0,0 1,83 82z"
android:strokeColor="@color/indigo_400"
android:strokeWidth="@dimen/hashtag_stroke_width" />
<path
android:name="i_dot"
android:pathData="M94,59m-14,0a14,14 0,1 1,28 0a14,14 0,1 1,-28 0"
android:strokeColor="@color/indigo_400"
android:strokeWidth="@dimen/hashtag_stroke_width" />
<path
android:name="o"
android:pathData="M159.5,119.5m-37.5,0a37.5,37.5 0,1 1,75 0a37.5,37.5 0,1 1,-75 0"
android:strokeColor="@color/indigo_400"
android:strokeWidth="@dimen/hashtag_stroke_width" />
</vector>
3. Javascript
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
4. Gist
package im.ene.lab.android.widgets; | |
import android.content.Context; | |
import android.graphics.PorterDuff; | |
import android.graphics.drawable.Drawable; | |
import android.os.Build; | |
import android.support.annotation.NonNull; | |
import android.support.annotation.Nullable; | |
import android.support.design.widget.TabLayout; | |
import android.util.AttributeSet; | |
import android.util.SparseArray; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.widget.ImageView; | |
import android.widget.TextView; | |
import im.ene.lab.android.R; | |
/** | |
* Created by eneim on 9/2/15. | |
* <p/> | |
* A custom TabLayout with Builder support for customizing Tabs. | |
* <p/> | |
* Since every Tab must be attached to a parent TabLayout, it's reasonable to have an inner | |
* Builder for every Tab in TabLayout, but not a global TabLayout#Builder. Builder is not strictly | |
* follow Builder design pattern. | |
*/ | |
public class BadgeTabLayout extends TabLayout { | |
private final SparseArray<Builder> mTabBuilders = new SparseArray<>(); | |
public BadgeTabLayout(Context context) { | |
super(context); | |
} | |
public BadgeTabLayout(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
} | |
public BadgeTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
} | |
public Builder with(int position) { | |
Tab tab = getTabAt(position); | |
return with(tab); | |
} | |
/** | |
* Apply a builder for this tab. | |
* | |
* @param tab for which we create a new builder or retrieve its builder if existed. | |
* @return the required Builder. | |
*/ | |
public Builder with(Tab tab) { | |
if (tab == null) { | |
throw new IllegalArgumentException("Tab must not be null"); | |
} | |
Builder builder = mTabBuilders.get(tab.getPosition()); | |
if (builder == null) { | |
builder = new Builder(this, tab); | |
mTabBuilders.put(tab.getPosition(), builder); | |
} | |
return builder; | |
} | |
public static final class Builder { | |
/** | |
* This badge widget must not support this value. | |
*/ | |
private static final int INVALID_NUMBER = Integer.MIN_VALUE; | |
@Nullable final View mView; | |
final Context mContext; | |
final TabLayout.Tab mTab; | |
@Nullable TextView mBadgeTextView; | |
@Nullable ImageView mIconView; | |
Drawable mIconDrawable; | |
Integer mIconColorFilter; | |
int mBadgeCount = Integer.MIN_VALUE; | |
boolean mHasBadge = false; | |
/** | |
* This construct take a TabLayout parent to have its context and other attributes sets. And | |
* the tab whose icon will be updated. | |
* | |
* @param parent | |
* @param tab | |
*/ | |
private Builder(TabLayout parent, @NonNull TabLayout.Tab tab) { | |
super(); | |
this.mContext = parent.getContext(); | |
this.mTab = tab; | |
// initialize current tab's custom view. | |
if (tab.getCustomView() != null) { | |
this.mView = tab.getCustomView(); | |
} else { | |
this.mView = LayoutInflater.from(parent.getContext()) | |
.inflate(R.layout.tab_custom_icon, parent, false); | |
} | |
if (mView != null) { | |
this.mIconView = (ImageView) mView.findViewById(R.id.tab_icon); | |
this.mBadgeTextView = (TextView) mView.findViewById(R.id.tab_badge); | |
} | |
if (this.mBadgeTextView != null) { | |
this.mHasBadge = mBadgeTextView.getVisibility() == View.VISIBLE; | |
try { | |
this.mBadgeCount = Integer.parseInt(mBadgeTextView.getText().toString()); | |
} catch (NumberFormatException er) { | |
er.printStackTrace(); | |
this.mBadgeCount = INVALID_NUMBER; | |
} | |
} | |
if (this.mIconView != null) { | |
mIconDrawable = mIconView.getDrawable(); | |
} | |
} | |
/** | |
* The related Tab is about to have a badge | |
* | |
* @return this builder | |
*/ | |
public Builder hasBadge() { | |
mHasBadge = true; | |
return this; | |
} | |
/** | |
* The related Tab is not about to have a badge | |
* | |
* @return this builder | |
*/ | |
public Builder noBadge() { | |
mHasBadge = false; | |
return this; | |
} | |
/** | |
* Dynamically set the availability of tab's badge | |
* | |
* @param hasBadge | |
* @return this builder | |
*/ | |
// This method is used for DEBUG purpose only | |
/*hide*/ | |
public Builder badge(boolean hasBadge) { | |
mHasBadge = hasBadge; | |
return this; | |
} | |
/** | |
* Set icon custom drawable by Resource ID; | |
* | |
* @param drawableRes | |
* @return this builder | |
*/ | |
public Builder icon(int drawableRes) { | |
mIconDrawable = getDrawableCompat(mContext, drawableRes); | |
return this; | |
} | |
/** | |
* Set icon custom drawable by Drawable Object | |
* | |
* @param drawable | |
* @return this builder | |
*/ | |
public Builder icon(Drawable drawable) { | |
mIconDrawable = drawable; | |
return this; | |
} | |
/** | |
* Set drawable color. Use this when user wants to change drawable's color filter | |
* | |
* @param color | |
* @return this builder | |
*/ | |
public Builder iconColor(Integer color) { | |
mIconColorFilter = color; | |
return this; | |
} | |
/** | |
* increase current badge by 1 | |
* | |
* @return this builder | |
*/ | |
public Builder increase() { | |
mBadgeCount = | |
mBadgeTextView == null ? | |
INVALID_NUMBER | |
: | |
Integer.parseInt(mBadgeTextView.getText().toString()) + 1; | |
return this; | |
} | |
/** | |
* decrease current badge by 1 | |
* | |
* @return | |
*/ | |
public Builder decrease() { | |
mBadgeCount = | |
mBadgeTextView == null ? | |
INVALID_NUMBER | |
: | |
Integer.parseInt(mBadgeTextView.getText().toString()) - 1; | |
return this; | |
} | |
/** | |
* set badge count | |
* | |
* @param count expected badge number | |
* @return this builder | |
*/ | |
public Builder badgeCount(int count) { | |
mBadgeCount = count; | |
return this; | |
} | |
/** | |
* Build the current Tab icon's custom view | |
*/ | |
public void build() { | |
if (mView == null) { | |
return; | |
} | |
// update badge counter | |
if (mBadgeTextView != null) { | |
mBadgeTextView.setText(formatBadgeNumber(mBadgeCount)); | |
if (mHasBadge) { | |
mBadgeTextView.setVisibility(View.VISIBLE); | |
} else { | |
// set to View#INVISIBLE to not screw up the layout | |
mBadgeTextView.setVisibility(View.INVISIBLE); | |
} | |
} | |
// update icon drawable | |
if (mIconView != null && mIconDrawable != null) { | |
mIconView.setImageDrawable(mIconDrawable.mutate()); | |
// be careful if you modify this. make sure your result matches your expectation. | |
if (mIconColorFilter != null) { | |
mIconDrawable.setColorFilter(mIconColorFilter, PorterDuff.Mode.MULTIPLY); | |
} | |
} | |
mTab.setCustomView(mView); | |
} | |
} | |
private static Drawable getDrawableCompat(Context context, int drawableRes) { | |
Drawable drawable = null; | |
try { | |
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | |
drawable = context.getResources().getDrawable(drawableRes); | |
} else { | |
drawable = context.getResources().getDrawable(drawableRes, context.getTheme()); | |
} | |
} catch (NullPointerException ex) { | |
ex.printStackTrace(); | |
} | |
return drawable; | |
} | |
/** | |
* This format must follow User's badge policy. | |
* | |
* @param value of current badge | |
* @return corresponding badge number. TextView need to be passed by a String/CharSequence | |
*/ | |
private static String formatBadgeNumber(int value) { | |
if (value < 0) { | |
return "-" + formatBadgeNumber(-value); | |
} | |
if (value <= 10) { | |
// equivalent to String#valueOf(int); | |
return Integer.toString(value); | |
} | |
// my own policy | |
return "10+"; | |
} | |
} |
Conclusion
Well, let's be multi-lang.