現場ドライバー発！Nusa ToyotetsuのDXを支えるSwiftUI多言語化アプリ開発の記録

## 💡 なぜこの記事を書くのか？

デジタルトランスフォーメーション（DX）が求められる時代、私はNusa Toyotetsuで日本人社長の専属ドライバーとして働きながら、自ら現場課題の解決に挑戦してきました。

運転手という立場だからこそ見える「現場の非効率」を、ITの力でどう変革できるかそれが本記事執筆のきっかけです。

---

## 🚗 ユニークなバックグラウンド —— 社長専属ドライバー、そして工学部生としての挑戦

👨‍💻 私はインドネシアの大学でコンピュータ工学を専攻し、在学中からNusa Toyotetsuで日本人社長の専属ドライバーとして働いています。

Nusa Toyotetsuはトヨタグループの中核企業である豊田鉄工（Toyoda Iron Works）傘下の自動車部品メーカーです。

現場で働く中、管理部門やドライバー、経営層が直面する様々な業務課題を間近で見てきました。

---

## 🎯 個人ミッション —— 卒業プロジェクトとしてのDriveMateと現場DXへの貢献

私は大学の卒業課題（最終プロジェクト）として「DriveMate」というモバイルアプリの開発を選択しました。

これは、Nusa Toyotetsuの人事・総務部門のDXを推進するため、現場の課題を自分のITスキルで解決する挑戦です。

多言語化（Localization）機能の実装から始め、現場の「困った」を一つずつITで解決するのが私のミッションです。

本記事では、非ITバックグラウンドからDX推進に挑戦し、大学卒業プロジェクトとしてDriveMateに取り組んだリアルな体験を共有します。

---

## 🛠️ DriveMate：アイデアからプロトタイプへ

### 現場のリアルな課題 📝

Nusa Toyotetsuで日々働く中、私は多くの現場課題に直面してきました。

たとえば、紙ベースの出勤管理や車両点検記録は時間も手間もかかり、ミスや情報の抜け漏れも発生しやすい状況でした。

さらに、管理部門がドライバーの現在地や出勤状況をリアルタイムで把握できず、迅速な対応が難しいという問題もありました。

---

### なぜモバイルアプリなのか？ 📱

これらの課題を解決するためには、いつでもどこでも利用できる“モバイルアプリ”が最適だと考えました。

スマートフォンを活用すれば、運転手自身が手軽に出勤や点検記録を入力でき、管理部門もリアルタイムで情報を共有できます。

これにより、現場業務の効率化とペーパーレス化を同時に実現できると確信しました。

---

### 経営層や管理部門とのコミュニケーションプロセス 🤝

まずは日々一緒に働くGA（総務）スタッフに現場の困りごとを相談し、アイデアや試作品を共有しました。

GAスタッフからも前向きなフィードバックをもらい、少しずつDriveMateの形を整えていきました。

また、私は社長専属ドライバーという立場もあり、移動中などのタイミングでDriveMateの成果や現場の状況を個人的に社長にご報告しました。

社長はその取り組みに興味を持ち、現場目線でのデジタル化を温かく応援してくださいました。

---

## 🌐 DriveMateの技術的アプローチと多言語対応の実践

### DriveMateの主な機能と初期開発フェーズ（Sprint 0）について

DriveMateの開発は「現場で本当に使われる」ことを最優先に、段階的に進めています。

最初のフェーズでは  
**まず動くものを早く作って現場でフィードバックを得る**  
ことを重視し、日本語で言うと「試作・プロトタイプ」作りに近いイメージです。  
（アジャイル開発やScrumでいう「Sprint 0」に相当します）

この初期段階ではログイン画面をシンプルに設計し、  

- 各ユーザー（日本人管理者、インドネシア人ドライバー等）が直感的に使える  
- 多言語（日本語・英語・インドネシア語）切り替えに対応  

を最優先の開発ターゲットとしました。

---

DriveMateの主な機能は、

- ドライバーの出退勤管理  
- 車両の日常点検リスト入力  
- リアルタイムな位置情報の共有  
- 管理部門のためのペーパーレス業務化  

です。

---

### なぜ多言語対応が重要なのか？（現場での実例から）

Nusa Toyotetsuでは、国籍も役職も異なる多様なメンバーが働いています。

例えば、インドネシア人ドライバーが日本語しか表示されないアプリを操作すると、誤操作や困惑の原因になり得ます。

また、日本人管理者やHRスタッフが英語/日本語/インドネシア語のいずれかでスムーズに情報を確認したいという現場要望も多くあります。

こうした背景から、  
**「誰もが迷わず、母語で正確に操作できる」**  
ユーザーインターフェースが必須だと痛感し、多言語化を最初の実装目標に設定しました。

---

### 多言語対応・Localizableの実装手順（実践的チュートリアル）

#### 1. Localizable.stringsファイルの準備

Xcodeの「Localization」機能で、日本語・英語・インドネシア語ごとに`Localizable.strings`を用意。  
全UIテキストをここで集中管理します。

#### 2. .stringdictファイルによる複数形・動的テキスト対応

例：通知数・未読メッセージ数など、数値に応じて文言が自動変化する箇所には`.stringsdict`ファイルを設定。

#### 3. SwiftUIでの利用例

SwiftUIでは、ローカライズされたテキストを簡単に呼び出すことができます。

```swift
// Localizable.strings (English)
"login_title" = "DriveMate";
"login_email_placeholder" = "Email";
"login_password_placeholder" = "Password";
"login_button" = "Login";
"login_biometric" = "Login with FaceID/TouchID";
"login_error" = "Invalid email or password";
"login_invalid_email" = "Invalid email format";
"login_invalid_password" = "Password must be at least 4 characters";
```

```swift
// Localizable.strings (Japanese)
"login_title" = "DriveMate";
"login_email_placeholder" = "メールアドレス";
"login_password_placeholder" = "パスワード";
"login_button" = "ログイン";
"login_biometric" = "FaceIDまたはTouchIDでログイン";
"login_error" = "メールアドレスまたはパスワードが正しくありません";
"login_invalid_email" = "無効なメールアドレス形式";
"login_invalid_password" = "パスワードは4文字以上必要です";
```

```swift
// Localizable.strings (Indonesian)
"login_title" = "DriveMate";
"login_email_placeholder" = "Email";
"login_password_placeholder" = "Kata sandi";
"login_button" = "Masuk";
"login_biometric" = "Masuk dengan FaceID/TouchID";
"login_error" = "Email/password salah";
"login_invalid_email" = "Format email tidak valid";
"login_invalid_password" = "Password minimal 4 karakter";
```

SwiftUIの画面では、次のように記述するだけで、ユーザーの端末言語設定に応じてボタンテキストなどが自動的に切り替わります。
```swift
Text(LocalizedStringKey("login_button"))
```

#### ログイン画面の多言語対応例

下記は、英語・インドネシア語・日本語それぞれの端末設定に応じたログイン画面例です。

##### ログイン画面（英語）
![Login Screen - English](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4060857/3a5cbd76-e702-435b-ad56-919754803317.png)

##### ログイン画面（インドネシア語）
![Login Screen - Indonesian](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4060857/1d25e729-85f5-453e-8349-87eab6b3f117.png)

##### ログイン画面（日本語）
![Login Screen - Japanese](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4060857/70b3d959-368f-4843-9ef0-8d42f1228942.png)

#### 4. 🔍 コードレビューと設計へのこだわり — 「現場ドライバー」目線のSwiftUI開発

DriveMateの開発では、「現場ドライバー」として実際に直面した課題や気付きから設計・実装方針を決めました。

具体的には、
- **プロジェクト構造**をシンプルかつスケーラブルに保つこと
- **可読性**と**保守性**を意識し、命名規則や責務分割に注意すること
- 現場で素早く修正・拡張できるよう、**疎結合な設計**を心掛けること

これらを意識してコーディングしました。

また、開発中はGAスタッフや日本人の上司（社長）に随時レビューを依頼し、  
「日本人が直感的に使える表現」や「現場で本当に役立つ機能」についてたくさんのフィードバックをもらいました。

とくに日本人社長からは「ボタン表記やエラー文の日本語表現をもっと自然に」といった指摘や、  
「画面遷移をよりシンプルに」など、実務に根ざした改善案が数多く寄せられました。

現場のリアルな声を受けてコードやUIを何度も修正し、  
「現場ファースト」のプロダクトづくりに近づけたのがDriveMate開発の大きな学びです。

##### プロジェクト構造の一例

DriveMate_SwiftUIプロジェクトでは、現場からの要求に即応できるよう、  
「可読性」「保守性」「スケーラビリティ」を重視したディレクトリ構成を採用しています。

下記は、実際のXcodeプロジェクトのフォルダ構成例です：

![project_structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4060857/8731b3f3-d246-42b7-8f27-57fd40b79f99.png)

- **App**: エントリーポイントやアプリ全体の状態管理
- **Core**
    - **DI**: 依存性注入の仕組み
    - **Theme**: カラーやタイポグラフィなどのテーマ設定
    - **Utils**: 汎用ユーティリティ
- **Data**
    - **DataSources**: 外部データ・APIアクセス層
    - **Models**: データモデル
    - **Repositories**: データ取得・保存のリポジトリ実装
- **Domain**
    - **Entities**: ビジネスロジック用エンティティ
    - **Repositories**: リポジトリプロトコル（抽象化）
    - **UseCases**: ユースケース・アプリの主要処理
- **Features**
    - **Attendance**: 出退勤機能
    - **Auth**: 認証機能（Intent, State, View, ViewModelで管理）
    - **Reporting**: レポート機能
    - **Tracking**: 位置情報トラッキング機能
- **Resources**
    - **Localizable**: 多言語対応ファイル（英語、日本語、インドネシア語）
    - **InfoPlist**: 各言語ごとのInfoPlist設定

この構成により、各機能ごとに役割が明確になり、  
将来的な機能追加や現場からのフィードバック反映がしやすくなっています。

##### LoginScreen.swiftの実装例 💻

DriveMateのログイン画面は、現場のドライバーや管理者が直感的に操作できるよう、  
シンプルで可読性の高いSwiftUIコードで設計しています。

```swift
import SwiftUI

struct LoginScreen: View {
    @EnvironmentObject var appState: AppState
    @StateObject private var viewModel: AuthViewModel
    @State private var shake: CGFloat = 0

    init(viewModel: AuthViewModel) {
        _viewModel = StateObject(wrappedValue: viewModel)
    }

    var body: some View {
        ZStack {
            Color("Background").ignoresSafeArea()
            VStack(spacing: 36) {
                // ...（一部省略）
                Text(NSLocalizedString("login_title", comment: ""))
                    .font(Typography.title)
                    .foregroundStyle(Color("BrandMain"))

                VStack(spacing: 18) {
                    VStack(spacing: 14) {
                        TextField(NSLocalizedString("login_email_placeholder", comment: ""), text: Binding(
                            get: { viewModel.state.email },
                            set: { viewModel.handle(.emailChanged($0)) }
                        ))
                        .keyboardType(.emailAddress)
                        .autocapitalization(.none)
                        .padding(14)
                        // ...スタイル省略

                        SecureField(NSLocalizedString("login_password_placeholder", comment: ""), text: Binding(
                            get: { viewModel.state.password },
                            set: { viewModel.handle(.passwordChanged($0)) }
                        ))
                        .autocapitalization(.none)
                        .padding(14)
                        // ...スタイル省略
                    }
                    .padding()
                    .background(Color("Surface").opacity(0.95))
                    .clipShape(RoundedRectangle(cornerRadius: 18))

                    // エラー表示
                    if let error = viewModel.state.errorMessage, !error.isEmpty {
                        Text(error)
                            .foregroundColor(Color("Error"))
                            .font(Typography.subheadline)
                            .bold()
                            .transition(.move(edge: .top).combined(with: .opacity))
                            .modifier(ShakeEffect(shakes: shake))
                            .onAppear {
                                shake += 1
                                UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
                            }
                    }
                }
                .animation(.easeInOut, value: viewModel.state.errorMessage)

                // ログインボタン
                Button(action: {
                    UIImpactFeedbackGenerator(style: .medium).impactOccurred()
                    viewModel.handle(.loginTapped)
                }) {
                    ZStack {
                        RoundedRectangle(cornerRadius: 12)
                            .fill(viewModel.state.isLoading ? Color("BrandMain").opacity(0.7) : Color("BrandMain"))
                            .frame(height: 48)
                        if viewModel.state.isLoading {
                            ProgressView()
                        } else {
                            Text(NSLocalizedString("login_button", comment: ""))
                                .fontWeight(.semibold)
                        }
                    }
                }
                .disabled(viewModel.state.isLoading)
                .opacity(viewModel.state.isLoading ? 0.7 : 1.0)
            }
            .padding(.horizontal, 28)
        }
    }
}
```
---
**ポイントまとめ:**
- プロジェクト構造は役割ごとに明確に分割
- SwiftUI + Localizableで多言語対応をシンプルに実装
- 実際の現場フィードバックを素早く反映する体制
---
#### 5. 🧪 現場ドライバーによる初期テストとフィードバック

Sprint 0段階では、DriveMateの基本機能（ログイン画面・多言語切替など）が正しく動作するかどうかを  
自分自身のiPhoneと現場ドライバー仲間の端末で手動テストしました。

また、Nusa Toyotetsuの管理部門や日本人社長にも実際に画面を見せながら、  
「日本語表示の分かりやすさ」や「誤操作の防止」について率直なフィードバックをもらうことができました。

**ポイントまとめ:**
- 実機テストによる初期バグ検出
- 現場ユーザー（管理部門・ドライバー・社長）から直接意見を収集
- 「現場ファースト」の改善サイクルをSprint 0から意識

---

#### 6. ドキュメントとバージョン管理への取り組み

現段階ではローカル環境中心で開発を進めており、  
設計メモや仕様変更の履歴はNotionやテキストファイルで管理しています。

今後、開発が本格化し機能追加やチーム開発のフェーズに入った際には、  
**GitHub**等のバージョン管理システムを活用し、履歴管理やコラボレーションも推進していく予定です。

**ポイントまとめ:**
- Sprint 0では主にローカルPCとNotionでドキュメントを管理
- バージョン管理の重要性は認識しており、今後はGitHub移行も視野
- シンプルなツールから始め、段階的に体制を強化

---
#### 7. ⚡「スタンバイドライバー×モバイル開発」両立への挑戦

Nusa Toyotetsuの専属ドライバーとして、日々の送迎業務や待機時間を最大限に活用し、  
DriveMateの開発と自己学習（SwiftUI・設計・日本語など）を両立させています。

特に、待機時間や移動の合間にはXcodeでコーディング、またはオンライン教材・技術記事で新しい知識をインプット。  
日本語の勉強については、「Mazii」アプリを使って日々語彙力を強化し、毎晩19時〜21時にはオンライン日本語コースで会話力アップにも取り組んでいます。

もちろん、いつ社長から呼び出しがあっても即座に対応できるようにスケジュールを柔軟に調整し、  
休憩や仮眠も必ず確保して安全運転を最優先、  
お客様（社長）に対して最高のホスピタリティを心掛けています。

**ポイントまとめ:**
- ドライバー業務と開発・自己研鑽をバランス良く両立
- スタンバイ時間と夜間を活用して日本語学習
- 安全・ホスピタリティ・プロフェッショナル意識を常に維持

---

#### 8. 🌱 IT未経験から開発者を目指す方へ — 実体験からのアドバイス

実は私は高校（SMK）時代からIT分野を学んできましたが、  
インドネシアでのさまざまな状況により、一度はドライバー職に就くことを決意しました。  
しかし「ITは自分の好きな分野だ」と信じ、現在も大学でIT専攻を続けています。

現場で日々問題解決を求められるドライバー経験と、  
ITで人の役に立ちたいという想いの両方がDriveMate開発の原動力になっています。

最初は分からないことも多かったですが、  
- 毎日一歩ずつ前進すること
- 現場の声を大切にすること
- 失敗やトライ＆エラーを恐れず挑戦すること

この3つを意識すれば、どんな背景からでも必ず成長できると実感しています。

**ポイントまとめ:**
- IT教育のバックグラウンドを活かしつつ、現場経験も強みに
- 小さな一歩の積み重ねが成果につながる
- “好き”を諦めずに挑戦し続けることが大事

---

#### 9. 🙇‍♂️ おわりに — 気軽にコメント・ディスカッションください！

最後までご覧いただき、本当にありがとうございました。  
この記事を通じて、現場発のDXやモバイルアプリ開発、  
SwiftUIや多言語化に少しでも興味を持っていただけたら嬉しいです。

私はまだまだ未熟な点も多く、学ぶことばかりですが、  
現場で得たリアルな気づきや挑戦を正直に発信し、  
同じような立場や悩みを持つ方とつながれればとても嬉しいです。

質問・フィードバック・雑談など、どんなことでもお気軽にコメントいただけると励みになります。  
皆さんと一緒に成長できる仲間として、今後ともどうぞよろしくお願いします！

---