プログラミング」カテゴリーアーカイブ

Teamsのマイクミュートをワンクリックで切り替える方法

Microsoft TeamsでWeb会議をよくするのですが、マイクのミュートの切り替えが面倒に感じていました。会議中にTeamsとは別のウィンドウを開いていることが多く、Teamsのウィンドウをアクティブに戻してからミュートを切り替えるのが非常に手間でした。

今回は、Teamsのウィンドウがアクティブでなくても、ワンクリックでマイクのミュートを切り替える方法を紹介します。実施環境のOSはWindowsです。

設定手順

まずAutoHotKeyのv2.0をインストールします。以下のURLを開いてDowloadボタンからインストーラをダウンロードして、インストールします。

https://www.autohotkey.com/

ToggleMuteTeams.ahk という名前のファイルを任意の場所に作成し、以下のスクリプトを記載します。.ahkファイルはAutoHotKeyのスクリプトファイルです。

F1::toggle_mute_teams()
toggle_mute_teams() {
winList := WinGetlist("ahk_exe Teams.exe",,,)
for window in winList {
if (WinGetTitle(window) != "Microsoft Teams Notification" and WinGetTitle(window) != "") {
WinActivate(WinGetTitle(window))
Send("^+M")
sleep 20 ; sometimes it activates the new window before the sendkeys finishes, delay fixes it
break ; mute shortcut works in both windows, so break after first send
}
}
}

作成した ToggleMuteTeams.ahk をダブルクリックして起動します。これで、会議中に「F1」キーを押してTeamsのマイクミュートを切り替えられるようになりました。Teamsのウィンドウがアクティブでない状態で「F1」キーを押すと、Teamsのウィンドウがアクティブになってからマイクミュートが切り替わります。

PCを起動し直した場合は、このスクリプトを再度実行する必要があります。ToggleMuteTeams.ahk のショートカットを以下のフォルダに配置することで、PC起動時に実行されるようになります。

C:\Users\<ユーザー名>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

トラックボールのボタンへのキー割り当て

「F1」キーを押すことでマイクのミュートを切り替えられるようになりましたが、もっと簡単に、トラックボールのボタンで切り替えられるようにしました。

自分はLogicoolのトラックボールを使っていて、Logi Options+というPCアプリでボタンの動作をカスタマイズできます。トラックボールのミドルクリック(スクロールホイールを押す動作)に「F1」キーボタンを割り当てました。これにより、トラックボールから手を離さずにミドルクリックを押すことで、マイクのミュートが切り替えられるようになりました。

ミュート切り替えボタンの変更

「F1」キーを押すには両手を使う必要があり、間違って押す可能性が低いため「F1」キーを設定しました。上記スクリプトの1行目を書き換えることで、別のキーを割り当てることもできます。AutoHotKeyのキーリストから設定できるキー名が確認できます。例えば、Insertキーに割り当てたい場合は、以下のように記載します。スクリプトを変更した場合は、変更を反映するために ToggleMuteTeams.ahk を再実行してください。

Insert::toggle_mute_teams()

補足

Teamsはチャットのウィンドウと会議のウィンドウが別に開きます。どちらのウィンドウがアクティブになっているのかAutoHotKeyは判定できないため、上記のスクリプトでは両方のウィンドウをアクティブにしてマイクミュート切り替えのボタン「Ctrl + Shift + M」を入力しています。現状では、チャットのウィンドウではこのボタンを押しても何も起こらないため問題ないですが、今後意図しない動作になる可能性があるため注意が必要です。

AutoHotKeyのスクリプトは以下のスクリプトを参考にしました。
https://github.com/randyklein/muteteams/blob/main/TeamsMute-ahkv2.ahk

自作のWebアプリをAndroidアプリ化できなかった

先日、風邪ログというWebアプリをリリースしました。自分の体調を記録して振り返り、自分がどんなときに体調が悪くなるかを理解できるアプリです。Webアプリですが、Google Playストアでもアプリを見つけてもらえるように、Androidアプリ化できるか試してみました。Androidアプリ化の方法はいくつかありますが、今回は一番さくっとできそうなWebViewを使った方法を試しました。

WebViewで動かしてみる

WebViewを使ってWebアプリを動かす方法を、Android公式の記事を参考に試しました。

https://developer.android.com/guide/webapps/webview?hl=ja

まずはAndroid Studioで新規プロジェクトを作成し、Empty ActivityをTemplateとして選びます。この初期状態でアプリを実行すると、空の画面が表示されます。

まずはactivity_main.xmlにWebViewを記載します。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- 以下のブロックを記載 -->
    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

次に、インターネット上で動くWebアプリにアクセスできるよう、AndroidManifest.xmlに許可設定を記載します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.isolity.kazelog">

    <!-- 以下のブロックを記載 -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        ...

最後に、MainActivity.ktにWebViewの設定を記載します。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 以降の行を記載
        val myWebView: WebView = findViewById(R.id.webview)
        // Webアプリ内のリンクをWebView内で開くようにする
        myWebView.webViewClient = WebViewClient()
        // JavaScriptの有効化
        myWebView.settings.javaScriptEnabled = true
        // Web Storageの有効化
        myWebView.settings.domStorageEnabled = true
        // Webアプリの読み込みのため、風邪ログのURLを指定
        myWebView.loadUrl("https://kazelog.shoarai.com")
    }
}

上記のコードでアプリを実行すると、風邪ログのトップページが表示されました。

WebView上でGoogleログインできない

ここでやっかいなのが、以下の処理です。

// Webアプリ内のリンクをWebView内で開くようにする
myWebView.webViewClient = WebViewClient()

風邪ログは、EmailによるログインやGoogleアカウントによるログインをサポートしています。ログインをボタンを押したときに、Googleのログイン画面に遷移することになるんですが、上記の設定を行わないとGoogle ChromeなどのAndroid規定のブラウザでログイン画面が開いてしまいます。上記の設定をするとこで、ログイン画面がWebView内で開くようになります。

さてここで本題なのですが、GoogleはWebView内でのログインをサポートしていません。たとえWebView内でGoogleにログインしようとしても、以下のエラー画面が表示されます。

この件に関する公式のドキュメントを探したところ、以下の記事が見つかりました。やはりWebViewからGoogleログインに必要なOAuthリクエストが許可されなくなるとのことです。

「ウェブビュー」と言われる埋め込みブラウザから Google への OAuth リクエストは許可されなくなります。

https://developers-jp.googleblog.com/2016/09/modernizing-oauth-interactions-in-native-apps.html

ということでWebアプリである風邪ログを、Androidアプリとしてリリースすることはできませんでした。Googleログインを行わないWebアプリであれば、リリースできるかもしれませんね。

WordPressをAMP化し、Gistの埋め込みに対応した

このサイトはWordPressを使って作成していますが、表示速度の向上やSEO対策のためにAMP化しました。

WordPressのAMP化

AMP化は、以下のWordPress公式プラグインを使って簡単に実現できました。

AMP

またGoogle Analyticsの設定は、「AMP → 設定 → アナリティクス」の「種類:」に「googleanalytics」と入力します。すると「JSON構成:」が自動入力されるので、「UA-」で始まるトラッキングIDを設定すればOKです。

しかし、AMP化によって一つ問題が発生しました。AMP化したことにより、Gistの埋め込みコードが表示されなくなってしまったのです。

GistのAMP対応

Gistの埋め込みコードを、AMP化したWordPressに表示する方法として、ショートコードを使う方法を紹介します。そのために、JetpackというこれまたWordPress公式プラグインを使います。

Jetpackの「設定 → 執筆」にある「ショートコードを使って作成し、人気サイトからメディアを埋め込む」にチェックを入れます。これにより、ショートコードの機能が使えるようになります。

AMP化前は、カスタムHTMLで以下のコードを記載すればGistを埋め込めました。

<script src="https://gist.github.com/shoarai/a98cf027914d43139f4dbe579f726b8b.js"></script>

AMP化後は、ショートコードで以下のテキストを記載すればGistを埋め込めます。

[gist]a98cf027914d43139f4dbe579f726b8b[/gist]

ちなみに、ショートコードはGist以外にも、Facebookの投稿やTwitterのツイートなども埋め込めます。利用可能なショートコードは以下で確認できます。
https://jetpack.com/support/shortcode-embeds/

KotlinでToastをカスタマイズする

先日、ポップアップアラームという設定した時刻にポップアップを表示するアプリを作りました。このページではその中でもポップアップであるToastをカスタマイズして表示する処理について紹介します。

標準のToastを表示する

標準のToastは以下のコードで表示できます。

object ToastView {
    fun showToast(context: Context, message: String) {
        Toast.makeText(context, message, Toast.LENGTH_LONG).show()
    }
}

Toastをカスタマイズする

前述のような標準のToastだと目立たず、通知を見逃しやすいので、上の画像のようにToastのサイズやフォントサイズが大きい独自のレイアウトを作成します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ads="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="1">

    <TextView
        android:id="@+id/message"
        android:layout_width="320dp"
        android:layout_height="80dp"
        android:background="@drawable/toast_shape"
        android:gravity="center"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textColor="@color/colorBase"
        android:textSize="40sp" />

</LinearLayout>

上記の独自レイアウトを表示します。

object ToastView {
    fun showToast(context: Context, message: String) {
        val inflate = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        val view = inflate.inflate(R.layout.toast, null)
        val textView = view.findViewById(R.id.message) as TextView
        textView.text = text

        Toast(context).run {
            this.view = view
            duration = Toast.LENGTH_LONG
            setGravity(Gravity.BOTTOM, 0, 250)
            show()
        }
    }
}

KotlinでAndroidの時刻を検知する

先日、ポップアップアラームという設定した時刻にポップアップを表示するアプリを作りました。このページではその中でも時刻の検知の処理について紹介します。

アラームを開始、停止する

Androidアプリで、ある時刻になったら処理を実行したい場合、AlarmManagerを使用します。厳密には、ある時刻になったことを通知するのではなく、ある時間を経過したことを通知できます。
今回はアラームアプリなので、ある程度正確な時間を通知できるsetExact()を呼びます。通知はBroadcastReceiverで受け取ります。
以下のクラスでは、アラームを開始して1度だけ通知します。別のアラームがすでに実行中なら、実行中のアラームは停止し、新しいアラームが開始されます。

object OnceAlarmManager {
    private const val REQUEST_CODE = 0
    private var alarmManager: AlarmManager? = null
    private var pendingIntent: PendingIntent? = null

    fun startAlarm(context: Context, calendar: Calendar) {
        alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(context, AlarmBroadcastReceiver::class.java)
        pendingIntent = PendingIntent.getBroadcast(
                context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        alarmManager?.setExact(AlarmManager.RTC, calendar.timeInMillis, pendingIntent)
    }

    fun stopAlarm() {
        alarmManager?.cancel(pendingIntent)
    }
}

アラームアプリでは、1つのアラームで時刻通知したら次のアラームを開始する必要があるので、以下の通り、設定がONになっているアラームを開始します。

object WeeklyAlarmManager {
    fun startNextAlarm(context: Context) {
        var weeklyAlarms = WeeklyAlarmDataManager.weeklyAlarms
        if (WeeklyAlarmUtil.hasPowerOn(weeklyAlarms)) {
            val calendar = WeeklyAlarmUtil.getNextAlarmAsCalendar(weeklyAlarms)
            OnceAlarmManager.startAlarm(context, calendar)
        } else {
            OnceAlarmManager.stopAlarm()
        }
    }
}

アラーム通知を受け取る

前述の通り、BroadcastReceiverでアラーム通知を受け取ります。通知を受け取ったら、ポップアップを表示し、次のアラームを開始します。

class AlarmBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // Show popup.
        WeeklyAlarmManager.startNextAlarm(context)
    }
}

BroadcastReceiverをアプリに登録します。

<application
    ...
    <receiver android:name=".AlarmBroadcastReceiver">
    </receiver>
</application>

端末の再起動時やタイムゾーンの変更時に、アラームを再設定する

端末が再起動されたことやタイムゾーンが変更されたことを、BroadcastReceiverで受け取れるよう、actionを設定します。

<application
    ...
    <receiver android:name=".AlarmBroadcastReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.TIMEZONE_CHANGED" />
            <action android:name="android.intent.action.TIME_SET" />
            <action android:name="android.intent.action.DATE_CHANGED" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.PACKAGE_REPLACED" />
            <data
                android:path="com.isolity.toastalarm"
                android:scheme="package" />
        </intent-filter>
    </receiver>
</application>

上記で設定したactionの通知時にも、BroadcastReceiverのonReceive()が実行されます。アラーム通知の場合のみ(ここではactionがnullのとき)、ポップアップメッセージを表示します。

class AlarmBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == null) {
            // Show popup.
        }
        WeeklyAlarmManager.startNextAlarm(context)
    }
}

改善点

上記コードの改善点として、例えば、アラーム通知の判定がaction==nullだとわかりにくいので、アクション名つけた方がいいですね。あとはDIとかテストの方法はこれから調べます。

※Huaweiのスマホでの注意事項

Huaweiのスマホでは、以下の設定をしないと、アプリをKillしたあとにAlarmManagerの処理が止まってしまい、通知を受け取れなくなってしまいます。
設定 > 詳細設定 > バッテリーマネージャー > 保護されたアプリ > 対象のアプリをONにする
アプリごとに設定が必要なので、アプリを初めてインストールしたときに設定しておきます。僕はHuaweiのP8liteというスマホを使っていて、Emulatorでは動くのに実機で動かず非常に苦労しました。。Android開発者は各端末の知識も必要で大変ですね;

KotlinでAndroidアラームアプリを作った

Androidのアラームアプリを作ったときにやったこと、調べたことを紹介します。

作ったアプリ

ポップアップアラームという設定した時刻にポップアップを表示するアプリを作りました。
ストアに公開したので、ぜひ触ってみてください!

やったこと

Cordova/PhoneGapでAndroidアプリを作ったことはありますが、ネイティブで作るのは初めてなので、まずは言語仕様の理解から始めました。

  1. Kotlinの理解
  2. 開発環境の構築
  3. 実装
  4. アイコンの作成
  5. リリース

Kotlinの理解

開発言語として、JavaではなくKotlinを選びました。
型推論が欲しいこと、Null安全に興味があったこと、周囲の評判が良かったことが理由です。
※先日、KotlinがAndroidアプリ公式の開発言語に選ばれましたが、僕が選んだ後の話だったのでニュースを見たときは幸せを感じました笑
言語仕様は、その言語を書き始める前にざっと知っておいた方が効率がいいと思っているので、以下の本を読みました。

Kotlinスタートブック

その他、実装中にリスト操作について知りたいことが多かったので、以下の記事を参考にさせていただきました。

Qiita:Kotlin のコレクション使い方メモ

開発環境の構築

Android Studioのインストールは公式サイトより。
Android StudioへのKotlinの導入は、前述のKotlin本を参考にしました。取り急ぎこれで十分かと。
JavaのコードをコピペするとKotlinコードに自動的に変換されるのは、Kotlinの大きなメリットでもありますね。

実装

実装した主な機能は以下の通り。

Androidネイティブの実装は初めてということもあり、だいぶ詰まりました…
細かい部分も含めて、別ページで紹介しようと思います。

広告の実装

アプリにバナー広告を実装してみました。使ったのはGoogleのAdMobです。手順は公式サイトを参考のこと。Android Studioと連携できるのでほぼ自動で実装できました。

アイコンの作成

アイコンの作成には、Inkscapeという無料の画像編集ソフトを使いました。

リリース

リリースの際、APKに署名する必要がありますが、これもAndroid Studioからできます。
むしろ、アプリの説明文を記載するのがなかなか大変ですね。

かかった時間

ざっくりですが、開発にかかった実時間の内訳は以下のような感じ。
といっても期間としては3ヶ月ぐらいかかってます。途中、実装に詰まって手が止まりました;

  • Kotlinの理解 → 1日
  • 開発環境の構築 → 1日
  • 実装 → 2週間
  • アイコンの作成 → 半日
  • リリース → 1日

振り返り

Contextの扱いであったりResourceファイルの違いであったり、Android開発の知識が乏しく実装中に手が止まることが多かったです。実装する前にAndroidの基礎本を読んでおけばよかったかなと。
試しに広告を入れてみましたが、はじめから広告ありきで画面設計、実装しておけば、広告を表示する隙間をあとで開ける必要はなかったかなと。

IonicアプリのCordovaをアップデートする

久しぶりにGoogle Play Developer Consoleを見たら、アラートが表示されていました。
PhoneGap version alert
アラートが出ているのはIonicで作ったアプリです。このアプリが使用しているCordovaのバージョンに脆弱性があるようで、期限内にアップロードしてね、という通知でした。

Ionicのアップデート

Ionicが依存するCordovaのバージョンは、Ionicのバージョンによって決まります。よってIonicをアップデートする必要があります。

$ npm i -g ionic

アプリのアップデート

Ionicをアップデートしても、アプリはアップデートされないため、一度アプリを消してから作り直す必要があります。まずはアプリを削除します。

$ ionic platform rm android

アプリをビルドし直します。

$ ionic build android

今後も脆弱性が見つかるかもしれないので、都度アップデートする必要がありますね。

C++によるベクトルと行列の計算クラス

大学の研究で運動機構のシミュレーションをしていた時に作った、ベクトルや行列の計算をするクラスを紹介します。GitHubにあるのでダウンロードしてみて下さい。
https://github.com/shoarai/arith

使い方

ベクトルの計算

ベクトルを含んだ計算を演算子のオーバーロードを使って実現することで、数式のようにスッキリと書けます。例では3次元ベクトルを計算していますが、z成分の値を0にすれば2次元ベクトルの計算も問題なくできます。

#include "arith/src/arith.h"
using namespace arith;
int main()
{
    // 初期化
    Vector vecA;              // x=0, y=0,  z=0
    Vector vecB(1, 10, 100);  // x=1, y=10, z=100
    // 要素の設定
    vecA.set(1, 2, 3);
    // 要素の取得
    double x = vecA.getx();
    double y = vecA.gety();
    double z = vecA.getz();
    Vector vecC;
    // ベクトル同士の加減算
    vecC  = vecA + vecB;
    vecC += vecA;
    vecC  = vecA - vecB;
    vecC -= vecA;
    int val = 10;
    // ベクトルとスカラの乗除算
    vecC  = vecA * val;
    vecC *= val;
    vecC  = vecA / val;
    vecC /= val;
    // 外積
    vecC = vecA * vecB;
    // 内積
    double inner = vecA % vecB;
    // ノルム
    double norm = vecA.norm();
    // 正規化
    vecC = vecA.normalize();
    return 0;
}

行列の計算

次は行列の計算!

#include "arith/src/arith.h"
using namespace arith;
int main
{
    // 初期化
    Matrix matA(3, 2);    // 3行2列行列
    Matrix matB(3, 2);    // 3行2列行列
    // 要素の設定
    matA(0, 1) = 10;
    matB(2, 1) = 20;
    // 要素の取得
    double a01 = matA(0, 1);
    double b21 = matB(2, 1);
    // 行列同士の加減算
    Matrix matC = matA + matB;
    matC += matA;
    matC  = matA - matB;
    matC -= matB;
    Matrix matD(2, 3);
    // 行列同士の乗算
    Matrix matE = matD * matA;
    int val = 10;
    // 行列とスカラの乗除算
    matA  = matB * val;
    matA *= val;
    matA  = matB / val;
    matA /= val;
    // 転置行列
    matA = matD.transpose();
    return 0;
}

正方行列の計算

次は行列の中でも、行と列の数が同じ正方行列の計算です。正方行列にのみ定義されている値を計算できます。

#include "arith/src/arith.h"
using namespace arith;
int main
{
    // 初期化
    SquareMatrix smatA(3);    // 3行3列の正方行列
    Matrix matA(2, 2);
    // 正方行列への型変換
    SquareMatrix smatB = matA;
    // 行列式
    double det = smatA.det();
    // 逆行列
    if (det != 0) {
        SquareMatrix smatC = smatA.invrs(det);
    }
    return 0;
}

IonicによるAndroidアプリ作成の最低限

このページでは、UIフレームワークのIonicを使ったアプリの作成について紹介します。

Ionicのインストール

Ionicとその依存モジュールであるcordovaをインストールします。

$ npm install -g cordova ionic

アプリの新規作成

アプリ名はIonicAppとします。テンプレートは作りたいアプリに近いものを選ぶと楽です。

$ ionic start IonicApp blank

ionic start

実装

実装は作りたいアプリに応じてご自由に!!笑

デバッグ&実行

アプリ内のHTMLやJavascriptなどをPCでデバッグしたい場合、以下のコマンドを実行します。ファイルを更新すると自動でブラウザが更新されるのでとっても便利です。

$ ionic serve

モバイル機器でアプリを実行したい場合、対応するプラットフォームを追加します。iPhoneやiPadで実行したい場合、「android」ではなく「ios」となります。

$ ionic platform add android

追加したプラットフォームを実行します。

$ ionic run android

また、上記PCでの実行と同様に、HTMLやJavascriptを更新した時に、再ビルドなしで実行するためには、以下のコマンドを実行します。「-l」はlivereloadの意味です。このコマンドで実行した場合、HTMLなどのソースはモバイル機器にダウンロードされるのではなく、PCがWebサーバーとなってモバイル機器からアクセスされるようになります。

$ ionic run android -l

アイコンの作成

Ionicのアプリアイコンのファイルはresourcesフォルダ内にあります。icon.pngはホーム画面に表示されるアプリのアイコン画像であり、splash.pngはアプリ起動時に表示されるスプラッシュ画像です。これらのファイルをそれぞれ上書きしてから下記コマンドを実行すると、各プラットフォーム用のアイコンサイズに変換してくれます。

$ ionic resources

APK作成

作ったアプリをAndroidアプリとしてGoogle Playに公開するために、APKファイルを作成します。手順はPhoneGapアプリの公開用APKを作るをご覧ください。

多言語化

アプリが日本語で公開できるレベルになったら、ぜひ英語でも公開しましょう。一度仕組みを作ってしまえば簡単に多言語化できます。
まず多言語化を行うangular-translateと多言語ファイルを外部ファイルとして読み込むangular-translate-loader-static-filesをダウンロードします。

$ ionic add angular-translate
$ ionic add angular-translate-loader-static-files

これらのモジュールをHTMLから読み込みます。

<!-- www/index.html -->
<!DOCTYPE html>
  ...
  <script src="lib/angular-translate/angular-translate.min.js"></script>
  <script src="lib/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js"></script>
  ...
</html>

アプリのAngularJSモジュールとして追加します。

// www/js/app.js
angular.module('starter', [
 ... , 'pascalprecht.translate'
])
...

多言語ファイルの読み込み設定と言語の適用を行います。言語の適用はOSに設定された言語を使用し、もしその言語用のファイルがなければ変数defaultLangに入れた言語が適用されます。多言語ファイルはjs/langフォルダ内の言語コード.json(例:ja.json)を使用します。これで多言語化の準備は完了です。対応する言語を増やしたいときは、その言語の用語ファイルを追加するだけです。

// www/js/app.js
...
.config(function($translateProvider) {
  $translateProvider.useStaticFilesLoader({
    prefix: 'js/lang/',
    suffix: '.json'
  });
  var defaultLang = 'en';
  var getLang = function() {
    try {
      return (navigator.browserLanguage || navigator.language || navigator.userLanguage).substr(0, 2)
    } catch (e) {
      return defaultLang;
    }
  }
  $translateProvider.preferredLanguage(getLang());
  $translateProvider.fallbackLanguage(defaultLang);
})

実際に多言語化するためには、下記のような用語ファイルをlangフォルダに作ります。各メッセージにIDを振ります。

www/js/lang/ja.json
{
  "START": "はじめる",
  "GAMEOVER": "Game Over",
  "RESTART": "もう一度",
  "": ""
}
www/js/lang/en.json
{
 "START": "Start",
 "GAMEOVER": "Game Over",
 "RESTART": "Restart",
 "": ""
}

HTMLからこれらの用語を使うには、translate属性を追加してメッセージIDを指定します。

<!-- www/index.html -->
...
   <button class="button button-assertive" translate>START</button>
...

Javascriptから使うにはtranslateサービスを使います。

// controller.js
app.controller('Ctrl', ['$scope', '$translate', function($scope, $translate) {
  $scope.start = $translate('START');
}]);

終わりに

アプリ作成のために必要最低限のことを突っ走って書いてきましたが、他に必要だと思ったら適宜追記します。
僕自身はIonicを使って2つのアプリを作りました。動作の確認のためなど試しに触って見てください。
ぽりごん           Composition

kizAPIを使って関連語を取得する

このページでは、kizAPIを使ってある言葉の関連語を取得する方法を紹介します。

kizAPIとは

kizAPIとは、kizasi.jpというブログ検索サイトが提供しているWeb APIです。機能としては、ある言葉に関連する言葉を取得したり、今話題になっている言葉を取得できたりします。利用規約はここの最下部「利用規約」にあるので、利用する場合はあらかじめご確認ください。

関連語の取得

下記index.htmlをブラウザで表示すると、「sho」に関連する言葉が表示されます。依存モジュールとしてjQueryとjquery.xdomainajax.jsを利用しています。jquery.xdomainajax.jsはクロスドメイン対策で利用しています。ここからダウンロードして下さい。

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>kizAPI</title>
</head>
<body>
  <h1>Terms</h1>
  <div id="terms"></div>
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
  <script src="js/jquery.xdomainajax.js"></script>
  <script src="js/kizapi.js"></script>
  <script src="js/index.js"></script>
</body>
</html>

kizAPIにアクセスしているのがkizapi.jsです。引数で渡した検索語の関連語を取得できます。

// js/kizapi.js
/**
 * @fileOverview kizapi API
 */
(function(global) {
  'use strict';
  var kizapi = {
    /**
     * 関連語を取得する
     * @param    {string}   key   検索語&nbsp;
     * @param    {string}   span  '24':1日, '1w':1週間 or '1m':1月
     * @return   {object}         レスポンス
     * @property {string[]} terms 関連語
     */
    getRelatedTerms: function(key, span) {
      var defer = $.Deferred();
      if (this._reqFlag && this._reqId.abort) {
        this._reqId.abort();
      }
      this._reqFlag = true;
      var that = this;
      this._reqId = $.ajax({
        type: 'GET',
        url: 'http://kizasi.jp/kizapi.py',
        data: {
          span: span,
          kw_expr: key,
          type: 'coll'
        }
      }).done(function(data, statusText, jqXHR) {
        var xml = data.results[0];
        var json = $.parseXML(xml);
        var terms = [];
        $(xml).find('channel').find('item').each(function() {
          var item = $(this).text();
          var items = item.split(/\r?\n/g);
          var term = items[1].trim();
          terms.push(term);
        });
        defer.resolve({
          terms: terms
        });
      }).always(function() {
        that._reqFlag = false;
      });
      return defer.promise();
    },
    _reqId: {},
    _reqFlag: false
  };
  if ('process' in global) {
    module.exports = kizapi;
  }
  global.kizapi = kizapi;
}((this || 0).self || global));

kizAPIを呼び出しているのがindex.jsです。検索語「sho」の関連語を画面に表示しています。

// js/index.js
$(function() {
  var key = 'sho';
  kizapi.getRelatedTerms(key, '1m').done(function(data) {
   var html = '';
   var terms = data.terms;
   for (var i = 0, len = terms.length; i < len; i++) {
     html += '<span>' + terms[i] + '</span>;';
   }
   $('#terms').html(html);
 });
});

関連語取得サービス

上記のモジュールを利用して作ったのが「ke」です。「ke」は検索したいキーワードの関連語を表示してくれます。
ke

感想

以上、いろいろなWebAPIを試してみて、何に活用できるかを考えるのは楽しいですね。