はじめに
ギャルゲーの主人公にi18n-jsを使って個性を出させます。
- 今回使用するライブラリ
- redux
- redux-saga
- i18n-js
- react-router-dom
redux、redux-sagaの設定などはすでにしている前提で話を進めていきます。
口調わけをする
今回の本題です。ここ以外のパートは全部読み飛ばして大丈夫です。
口調わけをするために、タイトルにも書いてあるi18n-js
というライブラリを使用します。
このライブラリを使えば比較的簡単にアプリケーションを多言語に対応させることができます。
決してギャルゲーを作るためのライブラリではありません。
まずは口調ごとに文言を設定していきます。
export default {
expression: '通常',
greeting: 'こんにちは。',
words_end: 'だよ。',
is_your_name: 'の名前はなに?'
};
export default {
expression: '敬語',
greeting: 'どうも。',
words_end: 'です。',
is_your_name: 'の名前はなんですか?'
};
とりあえず通常の口調(ギャルゲー主人公によくある「〜だよ」「〜かな?」のような柔和な口調)と敬語を作成しました。
次に設定用のファイルを作成し、上記の口調をインポートしていきます。
import normal from './normal';
import formal from './formal';
const I18n = require('i18n-js');
I18n.fallbacks = false;
I18n.translations = {normal, formal};
export default I18n;
プレイヤーにより選択された口調をreducerで受け取ります、sagaでセットします。
import {SelectExpression, SELECT_EXPRESSION} from '../actions/selectExpression';
import setting from '../expression/setting';
type ExpressionAction = SelectExpression;
export interface ExpressionState {
expression: any;
}
const initialState: ExpressionState = {
expression: setting.currentLocale(),
};
const expressionReducer = (state = initialState, action: ExpressionAction) => {
switch (action.type) {
case SELECT_EXPRESSION:
return Object.assign({}, state, {
expression: setting.currentLocale()
});
default:
return state;
}
};
export default expressionReducer;
import {put, takeEvery} from 'redux-saga/effects';
import setExpression, {SET_EXPRESSION} from '../actions/setExpression';
import {SELECT_EXPRESSION} from '../actions/selectExpression';
import setting from '../expression/setting';
function* handleSetLanguage(action: ReturnType<typeof setExpression>) {
let expression = action.payload;
setting.locale = expression;
yield (localStorage.language = expression);
yield put({type: SELECT_EXPRESSION});
}
function* expressionSaga() {
yield takeEvery(SET_EXPRESSION, handleSetLanguage);
}
export default expressionSaga;
これで口調わけのロジックが完成しました。
主人公の設定画面を作成する
先程作成したロジックを使って、主人公の設定をしていきます。
名前、一人称、二人称、口調が設定できればこの世にいる大体のキャラクターの喋り方を網羅することができるはずです。
この4点を設定できる画面を作成してみましょう。
import * as React from 'react';
import { Link } from "react-router-dom";
import {connect} from 'react-redux';
import {Dispatch} from 'redux';
import {GlobalState} from '../reducers/index';
import setExpression from '../actions/setExpression';
import selectExpression from '../actions/selectExpression';
import selectPerson from '../actions/person';
interface StateProps {
selectExpression: typeof selectExpression;
}
interface DispatchProps {
setExpression: typeof setExpression;
selectPerson: typeof selectPerson
}
type Props = StateProps & DispatchProps;
interface State {
expression: string;
name: string,
first_person: string,
second_person: string
}
interface IMessageInputEvent extends React.FormEvent<HTMLInputElement> {
target: HTMLInputElement;
}
class Setting extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
expression: 'normal',
name: '',
first_person: '',
second_person: ''
};
this.nameChange = this.nameChange.bind(this)
this.firstPersonChange = this.firstPersonChange.bind(this)
this.secondPersonChange = this.secondPersonChange.bind(this)
}
public handleClick(expression: string) {
this.props.setExpression(expression);
this.props.selectPerson({
name: this.state.name,
first_person: this.state.first_person,
second_person: this.state.second_person,
});
}
public nameChange(e: IMessageInputEvent){
this.setState({
name: e.target.value
})
}
public firstPersonChange(e: IMessageInputEvent){
this.setState({
first_person: e.target.value
})
}
public secondPersonChange(e: IMessageInputEvent){
this.setState({
second_person: e.target.value
})
}
public render() {
return (
<>
<h1>初期設定</h1>
<p>名前:</p>
<input type='text' value={this.state.name} onChange={this.nameChange}></input>
<p>一人称:</p>
<input type='text' value={this.state.first_person} onChange={this.firstPersonChange}></input>
<p>二人称:</p>
<input type='text' value={this.state.second_person} onChange={this.secondPersonChange}></input>
<p>口調選択:</p>
<select name="color1" onChange={ e => this.setState({expression: e.target.value}) }>
<option value="normal">通常</option>
<option value="formal">敬語</option>
</select>
<br/>
<Link to="/game" onClick={this.handleClick.bind(this, this.state.expression)}>
START
</Link>
</>
);
}
}
const mapStateToProps = (state: GlobalState) => ({
selectExpression: state.expression.expression,
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
setExpression: (expression: string | null) => dispatch(setExpression(expression)),
selectPerson: (person: {name: string, first_person: string, second_person: string}) => dispatch(selectPerson(person)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Setting);
作りました。
テキストボックス、セレクトボックスに入力されたvalueをsetStateしてdispatchしてるだけなのでここはそんなに目新しいところはないですね。
適当に読み飛ばしてください。
この設定画面で得た値をゲーム画面に反映させます。
import * as React from 'react';
import './Game.css';
import {connect} from 'react-redux';
import {GlobalState} from '../reducers/index';
import img from './image.png'
import setting from '../expression/setting'
interface Props {
name: string,
first_person: string,
second_person: string
}
class Game extends React.Component<Props> {
public render() {
return (
<div className="container">
<h1>ゲーム画面</h1>
<img src={img} alt="img" />
<div className="name">{this.props.name}</div>
<div className="text">{setting.t('greeting')}{this.props.first_person}の名前は{this.props.name}{setting.t('words_end')}{this.props.second_person}{setting.t('is_your_name')}</div>
</div>
);
}
}
const mapStateToProps = (state: GlobalState) => ({
name: state.person.person.name,
first_person: state.person.person.first_person,
second_person: state.person.person.second_person,
});
export default connect(
mapStateToProps
)(Game);
i18n-jsで作成した口調を参照するには、以下のようにコードを書けば値を取得できます。
import setting from '../expression/setting'
{setting.t('greeting')}
これで設定画面で得た主人公の情報をゲーム画面に反映させることができました。
ファイルさえ作れば口調は無限に増やすことができます。ためしに武士っぽい口調を足してみましょう。
export default {
expression: '武士口調',
greeting: 'ご機嫌よう。',
words_end: 'でござる。',
is_your_name: '、名は何と申す?'
};

おまけ
i18n-jsを使っている状態で定数ファイルから文言をimportするときはアロー関数を使わないといけないようです。
import setting from '../expression/setting';
export const greeting = () => {
return {
'通常': setting.t('normal-greeting'),
'チャラい': setting.t('chara-greeting'),
'根暗': setting.t('nekura-greeting'),
};
};
主人公の口調を変更した上で更にプレイヤーに主人公の性格もある程度選択させたい時などにご活用ください。