ソースコード source code

下記アプリの主要なソースコードを公開しています。アプリ開発の参考になれば幸いです。

画像等が別途必要ですので下記情報のみでアプリが完成するものではありません。 アプリは少しずつ機能拡張していますのでストア公開されているアプリと内容が異なる場合があります。 コードはコピーして自由にお使いいただけます。ただし著作権は放棄しておりませんので全部の再掲載はご遠慮ください。部分的に再掲載したり、改変して再掲載するのは構いません。 自身のアプリ作成の参考として個人使用・商用問わず自由にお使いいただけます。 コード記述のお手本を示すものではありません。ミニアプリですので変数名などさほど気遣いしていない部分も有りますし間違いも有るかと思いますので参考程度にお考え下さい。 他の賢者の皆様が公開されているコードを参考にした箇所も含まれます。Flutter開発の熟練者が書いたコードではありません。 エンジニア向け技術情報共有サービスではありませんので説明は省いています。 GitHubなどへの公開は予定しておりません。

下記コードの最終ビルド日: 2023-10-16

pubspec.yaml

name: grouping
description: "Grouping"
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=3.2.0-87.0.dev <4.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.5
  package_info_plus: ^4.1.0
  shared_preferences: ^2.0.17
  http: ^1.1.0
  flutter_localizations:    #多言語ライブラリの本体    # .arbファイルを更新したら flutter gen-l10n
    sdk: flutter
  intl: ^0.18.1     #多言語やフォーマッタなどの関連ライブラリ
  flutter_tts: ^3.8.2
  google_mobile_ads: ^3.1.0  #^2.4.0
  just_audio: ^0.9.35

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_launcher_icons: ^0.13.1    #flutter pub run flutter_launcher_icons
  flutter_native_splash: ^2.3.2     #flutter pub run flutter_native_splash:create

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.1

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

flutter_icons:
  android: "launcher_icon"
  ios: true
  image_path: "assets/icon/icon.png"
  adaptive_icon_background: "assets/icon/icon_back.png"
  adaptive_icon_foreground: "assets/icon/icon_fore.png"

flutter_native_splash:
  color: '#2A01AD'
  image: 'assets/image/splash.png'
  color_dark: '#2A01AD'
  image_dark: 'assets/image/splash.png'
  fullscreen: true
  android_12:
    icon_background_color: '#2A01AD'
    image: 'assets/image/splash.png'
    icon_background_color_dark: '#2A01AD'
    image_dark: 'assets/image/splash.png'

# The following section is specific to Flutter packages.
flutter:
  generate: true    #自動生成フラグの有効化

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  assets:
    - assets/image/
    - assets/image/back/
    - assets/image/alphabet/
    - assets/image/animal/
    - assets/sound/

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

ad_mob.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-02-06
///

import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io';

class AdMob {
  late BannerAd _adMobBanner;
  bool _isAdMob = false;
  AdMob() { //constructor
    String adBannerUnitId = '';
    if (!kIsWeb && Platform.isAndroid) {
      //adBannerUnitId = 'ca-app-pub-3940256099942544/6300978111';  //test
      adBannerUnitId = 'ca-app-pub-0000000000000000/0000000000';
      _isAdMob = true;
    } else if (!kIsWeb && Platform.isIOS) {
      //adBannerUnitId = 'ca-app-pub-3940256099942544/6300978111';  //test
      adBannerUnitId = 'ca-app-pub-0000000000000000/0000000000';
      _isAdMob = true;
    }
    if (_isAdMob) {
      _adMobBanner = BannerAd(
        adUnitId: adBannerUnitId,
        size: AdSize.banner,
        request: const AdRequest(),
        listener: const BannerAdListener(),
      );
    }
  }
  void load() {
    if (_isAdMob) {
      _adMobBanner.load();
    }
  }
  void dispose() {
    if (_isAdMob) {
      _adMobBanner.dispose();
    }
  }
  Widget getAdBannerWidget() {
    if (_isAdMob) {
      return Container(
        alignment: Alignment.center,
        width: _adMobBanner.size.width.toDouble(),
        height: _adMobBanner.size.height.toDouble(),
        child: AdWidget(ad: _adMobBanner),
      );
    } else {
      return Container();
    }
  }
  double getAdBannerHeight() {
    if (_isAdMob) {
      return _adMobBanner.size.height.toDouble();
    } else {
      return 150;  //for web
    }
  }
}

audio_play.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:just_audio/just_audio.dart';

import 'package:grouping/const_value.dart';

class AudioPlay {
  //音を重ねて連続再生できるようにインスタンスを用意しておき、順繰りに使う。
  static final List<AudioPlayer> _playerSet = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  static final List<AudioPlayer> _playerSlide = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  int _playerSetPtr = 0;
  int _playerSlidePtr = 0;

  double _soundVolume = 1.0;

  //constructor
  AudioPlay() {
    constructor();
  }
  void constructor() async {
    for (int i = 0; i < _playerSet.length; i++) {
      await _playerSet[i].setAsset(ConstValue.audioSet);
    }
    for (int i = 0; i < _playerSlide.length; i++) {
      await _playerSlide[i].setAsset(ConstValue.audioSlide);
    }
    playZero();
  }
  void dispose() {
    for (int i = 0; i < _playerSet.length; i++) {
      _playerSet[i].dispose();
    }
    for (int i = 0; i < _playerSlide.length; i++) {
      _playerSlide[i].dispose();
    }
  }
  //getter
  double get soundVolume {
    return _soundVolume;
  }
  //setter
  set soundVolume(double vol) {
    _soundVolume = vol;
  }
  //最初に音が鳴らないのを回避する方法
  void playZero() async {
    AudioPlayer ap = AudioPlayer();
    await ap.setAsset(ConstValue.audioZero);
    await ap.load();
    await ap.play();
  }
  //
  void playSet() async {
    _playerSetPtr += 1;
    if (_playerSetPtr >= _playerSet.length) {
      _playerSetPtr = 0;
    }
    await _playerSet[_playerSetPtr].setVolume(_soundVolume);
    await _playerSet[_playerSetPtr].pause();
    await _playerSet[_playerSetPtr].seek(Duration.zero);
    await _playerSet[_playerSetPtr].play();
  }
  void playSlide() async {
    _playerSlidePtr += 1;
    if (_playerSlidePtr >= _playerSlide.length) {
      _playerSlidePtr = 0;
    }
    await _playerSlide[_playerSlidePtr].setVolume(_soundVolume);
    await _playerSlide[_playerSlidePtr].pause();
    await _playerSlide[_playerSlidePtr].seek(Duration.zero);
    await _playerSlide[_playerSlidePtr].play();
  }
}

const_value.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///

import 'package:flutter/material.dart';

class ConstValue {
  //pref
  static const String prefLanguageCode = 'languageCode';
  static const String prefPeopleNumber = 'peopleNumber';
  static const String prefGroupNumber = 'groupNumber';
  static const String prefCardType = 'cardType';
  static const String prefCardRandomize = 'cardRandomize';
  static const String prefSoundVolume = 'soundVolume';
  //image
  static const List<String> imagesBack = [
    'assets/image/back/back0.webp',
    'assets/image/back/back1.webp',
    'assets/image/back/back2.webp',
    'assets/image/back/back3.webp',
    'assets/image/back/back4.webp',
    'assets/image/back/back5.webp',
    'assets/image/back/back6.webp',
    'assets/image/back/back7.webp',
    'assets/image/back/back8.webp',
    'assets/image/back/back9.webp',
  ];
  static const List<String> imagesAlphabet = [
    'assets/image/alphabet/a.webp',
    'assets/image/alphabet/b.webp',
    'assets/image/alphabet/c.webp',
    'assets/image/alphabet/d.webp',
    'assets/image/alphabet/e.webp',
    'assets/image/alphabet/f.webp',
    'assets/image/alphabet/g.webp',
    'assets/image/alphabet/h.webp',
    'assets/image/alphabet/i.webp',
    'assets/image/alphabet/j.webp',
    'assets/image/alphabet/k.webp',
    'assets/image/alphabet/l.webp',
    'assets/image/alphabet/m.webp',
    'assets/image/alphabet/n.webp',
    'assets/image/alphabet/o.webp',
    'assets/image/alphabet/p.webp',
    'assets/image/alphabet/q.webp',
    'assets/image/alphabet/r.webp',
    'assets/image/alphabet/s.webp',
    'assets/image/alphabet/t.webp',
    'assets/image/alphabet/u.webp',
    'assets/image/alphabet/v.webp',
    'assets/image/alphabet/w.webp',
    'assets/image/alphabet/x.webp',
    'assets/image/alphabet/y.webp',
    'assets/image/alphabet/z.webp',
  ];
  static const List<String> imagesAnimal = [
    'assets/image/animal/cheetah.webp',
    'assets/image/animal/giraffe.webp',
    'assets/image/animal/hebi.webp',
    'assets/image/animal/hitsuji.webp',
    'assets/image/animal/inoshishi.webp',
    'assets/image/animal/inu.webp',
    'assets/image/animal/kaba.webp',
    'assets/image/animal/koala.webp',
    'assets/image/animal/kojika.webp',
    'assets/image/animal/kuma.webp',
    'assets/image/animal/leopard.webp',
    'assets/image/animal/lion.webp',
    'assets/image/animal/nezumi.webp',
    'assets/image/animal/niwatori.webp',
    'assets/image/animal/ookami.webp',
    'assets/image/animal/pegasus.webp',
    'assets/image/animal/saru.webp',
    'assets/image/animal/suzume.webp',
    'assets/image/animal/tanuki.webp',
    'assets/image/animal/tatsu.webp',
    'assets/image/animal/tora.webp',
    'assets/image/animal/uma.webp',
    'assets/image/animal/usagi.webp',
    'assets/image/animal/ushi.webp',
    'assets/image/animal/wani.webp',
    'assets/image/animal/zou.webp',
  ];
  //color
  static const Color colorButtonBack = Color.fromARGB(60, 0,0,0);
  static const Color colorButtonFore = Color.fromARGB(255, 255, 255, 255);
  static const Color colorBack = Color.fromARGB(255, 42,1,173);
  static const Color colorSettingAccent = Color.fromARGB(255, 76,43,164);
  //sound
  static const String audioZero = 'assets/sound/zero.wav';    //無音1秒
  static const String audioSlide = 'assets/sound/slide.mp3';
  static const String audioSet = 'assets/sound/set.wav';
  //string
  static const Map<String,String> languageCode = {
    'en': 'English',
    'bg': 'български език',
    'cs': 'Čeština',
    'da': 'dansk',
    'de': 'Deutsch',
    'el': 'Ελληνικά',
    'es': 'Español',
    'et': 'eesti keel',
    'fi': 'Suomen kieli',
    'fr': 'Français',
    'hu': 'magyar nyelv',
    'it': 'Italiano',
    'ja': '日本語',
    'lt': 'lietuvių kalba',
    'lv': 'Latviešu',
    'nl': 'Nederlands',
    'pl': 'Polski',
    'pt': 'Português',
    'ro': 'limba română',
    'ru': 'русский',
    'sk': 'Slovenčina',
    'sv': 'svenska',
    'th': 'ภาษาไทย',
    'zh': '中文',
  };

}

game.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///

import 'package:grouping/preferences.dart';

class Game {

  //以下の変数は状態が変化する毎に更新すること。
  bool _cardReset = false;
  int _peopleNumber = 2;
  int _groupNumber = 2;
  int _cardType = 0;
  int _cardRandomize = 1;
  double _soundVolume = 1.0;

  //constructor
  Game() {
    _initialize();
  }
  //データを全て更新する
  Future<void> _initialize() async {
    _cardReset = false;
    _peopleNumber = await _getPeopleNumber();
    _groupNumber = await _getGroupNumber();
    _cardType = await _getCardType();
    _cardRandomize = await _getCardRandomize();
    _soundVolume = await _getSoundVolume();
  }

  //---------------------------------------------

  //リセットカード
  bool get cardReset {
    return _cardReset;
  }
  set cardReset(bool flag) {
    _cardReset = flag;
  }

  //---------------------------------------------

  //人数
  int get peopleNumber {
    return _peopleNumber;
  }
  set peopleNumber(int num) {
    _setPeopleNumber(num);
  }
  Future<void> _setPeopleNumber(int num) async {
    _peopleNumber = num;
    await Preferences.setPeopleNumber(num);
  }
  Future<int> _getPeopleNumber() async {
    int num = await Preferences.getPeopleNumber() ?? 2;
    if (num < 2) {
      num = 2;
    }
    return num;
  }

  //---------------------------------------------

  //グループ数
  int get groupNumber {
    return _groupNumber;
  }
  set groupNumber(int num) {
    _setGroupNumber(num);
  }
  Future<void> _setGroupNumber(int num) async {
    _groupNumber = num;
    await Preferences.setGroupNumber(num);
  }
  Future<int> _getGroupNumber() async {
    int num = await Preferences.getGroupNumber() ?? 2;
    if (num < 2) {
      num = 2;
    }
    return num;
  }

  //---------------------------------------------

  //カード種類
  int get cardType {
    return _cardType;
  }
  set cardType(int num) {
    _setCardType(num);
  }
  Future<void> _setCardType(int num) async {
    _cardType = num;
    await Preferences.setCardType(num);
  }
  Future<int> _getCardType() async {
    final int num = await Preferences.getCardType() ?? 0;
    return num;
  }

  //---------------------------------------------

  //カードランダム
  int get cardRandomize {
    return _cardRandomize;
  }
  set cardRandomize(int num) {
    _setCardRandomize(num);
  }
  Future<void> _setCardRandomize(int num) async {
    _cardRandomize = num;
    await Preferences.setCardRandomize(num);
  }
  Future<int> _getCardRandomize() async {
    final int num = await Preferences.getCardRandomize() ?? 1;
    return num;
  }

  //---------------------------------------------

  //効果音音量
  double get soundVolume {
    return _soundVolume;
  }
  set soundVolume(double num) {
    _setSoundVolume(num);
  }
  Future<void> _setSoundVolume(double num) async {
    _soundVolume = num;
    await Preferences.setSoundVolume(num);
  }
  Future<double> _getSoundVolume() async {
    final double num = await Preferences.getSoundVolume() ?? 1.0;
    return num;
  }

}

language_state.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

import 'package:grouping/preferences.dart';

class LanguageState {

  static String _languageCode = 'en';

  //言語コードを記録
  static Future<void> setLanguageCode(String str) async {
    _languageCode = str;
    await Preferences.setLanguageCode(_languageCode);
  }
  //言語コードを返す
  static Future<String> getLanguageCode() async {
    _languageCode = await Preferences.getLanguageCode() ?? 'en';
    return _languageCode;
  }

}

main.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-02
///

import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';    //########### Webの時はコメントアウトする。Android,iOSの時は有効にする
import 'package:flutter/foundation.dart' show kIsWeb;

//自身で作成したclassを読み込む
import 'package:grouping/const_value.dart';
import 'package:grouping/language_state.dart';
import 'package:grouping/version_state.dart';
import 'package:grouping/game.dart';
import 'package:grouping/setting.dart';
import 'package:grouping/ad_mob.dart';
import 'package:grouping/audio_play.dart';
import 'package:grouping/page_state.dart';
import 'package:grouping/stage_card.dart';

void main() {
  //MobileAds.instance.initialize(); ここに入れると進行しなかった
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {    //statefulに変更して言語変更に対応
  const MainApp({super.key});
  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  Locale localeLanguage = const Locale('en');
  @override
  Widget build(BuildContext context) {
    MobileAds.instance.initialize();    //######## Webの時はコメントアウトする。Android,iOSの時は有効にする
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      localizationsDelegates: AppLocalizations.localizationsDelegates,   //多言語化
      supportedLocales: AppLocalizations.supportedLocales,  //自動で言語リストを生成
      locale: localeLanguage,
      home: const MainHomePage(),
    );
  }
}

class MainHomePage extends StatefulWidget {
  const MainHomePage({super.key});
  @override
  State<MainHomePage> createState() => _MainHomePageState();
}

class _MainHomePageState extends State<MainHomePage> with SingleTickerProviderStateMixin {
  final AdMob _adMob = AdMob(); //広告表示
  final AudioPlay _sound = AudioPlay(); //音再生用
  final Game _game = Game();  //ゲームのデータを一時的に保持するなどの役目
  final Random _random = Random();
  double _screenWidth = 0.0;
  late AnimationController _animationController;
  late Animation<Offset> _offsetAnimation01;
  List<StageCard> _stageCards = []; //ステージ上のカードの各種情報を保持
  List<Widget> _displayCards = [];  //ステージ上のカードのイメージ配列
  List<Widget> _storeCards = [];    //めくったカードのイメージ配列
  int _cardState = 0;   //0:カードがめくれる状態 1:next表示 2:end
  bool _busyFlag = false; //動作中はtrue
  bool _firstAction = true; //アプリ起動後1回だけ実施される

  //アプリのバージョン取得
  void _getVersion() async {
    PackageInfo packageInfo = await PackageInfo.fromPlatform();
    setState(() {
      VersionState.versionSave(packageInfo.version);
    });
  }
  //言語準備
  void _getCurrentLocale() async {
    Locale locale = Locale(await LanguageState.getLanguageCode());
    if (mounted) {  //Widgetが存在する。Widgetが存在しない時の実行によるエラーを回避する為。
      context.findAncestorStateOfType<_MainAppState>()!
        ..localeLanguage = locale
        ..setState(() {});
    }
  }
  //ページ起動開始時に一度だけ呼ばれる
  @override
  void initState() {
    super.initState();
    _getVersion();
    _getCurrentLocale();
    LanguageState.getLanguageCode();
    _adMob.load();
    //
    _animationController = AnimationController(duration: const Duration(seconds: 1),vsync: this);
  }
  //ページ終了時に一度だけ呼ばれる
  @override
  void dispose() {
    PageState.setCurrentPage('');
    _adMob.dispose();
    _animationController.dispose();
    super.dispose();
  }
  //スタートボタン
  Widget _startButton() {
    String startButtonText = '';
    if (_cardState == 0) {
      startButtonText = AppLocalizations.of(context)!.flipTheCard;
    } else if (_cardState == 1) {
      startButtonText = AppLocalizations.of(context)!.nextCard;
    } else if (_cardState == 2) {
      startButtonText = AppLocalizations.of(context)!.end;
    } else {
    }
    return GestureDetector(
      onTap: () {
        if (_cardState == 0) {
          _cardFlip();
        } else if (_cardState == 1) {
          _cardStore();
        }
      },
      child: Opacity(
        opacity: _busyFlag ? 0.3 : 1, //抽選中は薄くする
        child: Container(
          width: double.infinity,
          padding: const EdgeInsets.all(12.0),
          color: ConstValue.colorButtonBack,
          child: Center(
            child: Text(startButtonText,
              style: const TextStyle(
                color: ConstValue.colorButtonFore,
                fontSize: 24.0,
              )
            )
          )
        )
      )
    );
  }
  //stage area
  Widget _stageArea() {
    return AspectRatio(
      aspectRatio: 1 / 1,
      child: LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          return Padding(
            padding: const EdgeInsets.only(top: 1, left: 10, right: 45, bottom: 45),
            child: Stack(children: [
              Stack(children: _displayCards),
              GestureDetector(
                onTap: () {
                  if (_cardState == 0) {
                    _cardFlip();
                  } else if (_cardState == 1) {
                    _cardStore();
                  }
                },
                child: SizedBox(
                  width: _screenWidth,
                  height: _screenWidth,
                  child: Container(color: Colors.transparent),
                )
              )
            ])
          );
        }
      )
    );
  }
  //store area
  Widget _storeArea() {
    return Column(children: _storeCards);
  }
  //画面全体
  @override
  Widget build(BuildContext context) {
    //このページが開いたときに一度だけ実行される処理を記述。initStateではタイミングが合わない為。
    if (PageState.getCurrentPage() != 'main') {
      PageState.setCurrentPage('main');
      _sound.soundVolume = _game.soundVolume;
      if (!kIsWeb && Platform.isAndroid) {
        //Androidの場合、一発目の音が出ないのでここで鳴らしておく。
        //final double lastSoundVolume = _sound.soundVolume;
        //_sound.soundVolume = 0;
        //_sound.playSlide();
        //_sound.playSet();
        //_sound.playSlide();
        //_sound.playSet();
        //_sound.playSlide();
        //_sound.playSet();
        //_sound.soundVolume = lastSoundVolume;
      } else if (!kIsWeb && Platform.isIOS) {
        //何もしない
      }
    }
    _screenWidth = MediaQuery.of(context).size.width;
    int lastPeopleNumber = _game.peopleNumber;
    int lastGroupNumber = _game.groupNumber;
    if (_firstAction) { //アプリ起動後1回だけ実行
      _firstAction = false;
      (() async {
        await Future.delayed(const Duration(seconds: 1));
        _sound.playZero();
        _initCard();
        setState(() {});
      })();
    }
    return Scaffold(
      appBar: AppBar(
        backgroundColor: ConstValue.colorBack,
        //タイトル表示
        title: const Text('Grouping',
          style: TextStyle(
            color: ConstValue.colorButtonFore,
            fontSize: 15.0,
          )
        ),
        //設定ボタン
        actions: <Widget>[
          TextButton(
            onPressed: () async {
              if (_busyFlag) {
                return;
              }
              bool? ret = await Navigator.of(context).push(
                MaterialPageRoute<bool>(
                  builder: (context) => SettingPage(game: _game),
                ),
              );
              //awaitで呼び出しているので、settingから戻ったら以下が実行される。
              if (ret!) { //設定で適用だった場合
                _getCurrentLocale();
                if (_game.cardReset) {
                  _game.cardReset = false;
                  _initCard();
                } else if (_game.peopleNumber != lastPeopleNumber || _game.groupNumber != lastGroupNumber) {
                  _initCard();
                }
                setState(() {});
              }
            },
            child: Opacity(
              opacity: _busyFlag ? 0.3 : 1,
              child: Text(
                AppLocalizations.of(context)!.setting,
                style: const TextStyle(
                  color: ConstValue.colorButtonFore,
                )
              )
            )
          )
        ]
      ),
      body: SafeArea(
        child: Container(
          color: ConstValue.colorBack,
          child: Column(children:[
            Expanded(
              child: SingleChildScrollView(
                child: Column(
                  children: <Widget>[
                    _stageArea(),
                    _startButton(), //STARTボタン
                    _storeArea(),
                  ]
                )
              )
            ),
            //広告
            Padding(
              padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
              child: SizedBox(
                width: double.infinity,
                child: _adMob.getAdBannerWidget(),
              )
            )
          ])
        )
      )
    );
  }
  //裏向きのカードを用意する
  void _initCard() {
    final List<int> groups = List.generate(26, (index) => index); //0..25の26個
    if (_game.cardRandomize == 1) {
      groups.shuffle();
    }
    final List<int> foreNumbers = [];
    for (int i = 0; i < _game.peopleNumber; i++) {
      //foreNumbersにグループを割り当てる
      foreNumbers.add(groups[i % _game.groupNumber]);
    }
    //出る順番をシャッフルして完了
    foreNumbers.shuffle();
    //
    final double offsetX = _screenWidth * 0.3 / _game.peopleNumber;
    final double offsetY = _screenWidth * 0.09 / _game.peopleNumber;
    final int cardBackImageIndex = _random.nextInt(ConstValue.imagesBack.length);
    _stageCards = [];
    int id = 0;
    for (int foreNumber in foreNumbers) {
      final int cardType = _game.cardType;
      final Offset offset = Offset(id * offsetX,id * offsetY);
      _stageCards.add(StageCard(id,cardBackImageIndex,cardType,foreNumber,offset));
      id += 1;
    }
    _displayCards = [];
    for (StageCard stageCard in _stageCards) {
      _displayCards.add(
          Transform.translate(
              offset: stageCard.cardOffset,
              child: Image.asset(ConstValue.imagesBack[stageCard.cardBackIndex])
          )
      );
    }
    _storeCards = [];
    _cardState = 0;
  }
  void _cardFlip() {
    if (_stageCards.isEmpty) {
      return;
    }
    if (_busyFlag) {
      return;
    }
    _busyFlag = true;
    _sound.playSlide();
    StageCard lastStageCard = _stageCards.last;
    _offsetAnimation01 = Tween<Offset>(
      begin: lastStageCard.cardOffset, // 開始位置
      end: Offset(- _screenWidth, lastStageCard.cardOffset.dy), // 終了位置
    ).animate(_animationController);
    //最後のカードを消して代わりのアニメーション付きを同じ位置に差し替える
    _displayCards.removeLast();
    _displayCards.add(
      AnimatedBuilder(
        animation: _animationController,
        builder: (BuildContext context, Widget? child) {
          return Transform.translate(
            offset: _offsetAnimation01.value,
            child: Image.asset(ConstValue.imagesBack[lastStageCard.cardBackIndex])
          );
        }
      )
    );
    _animationController.forward().whenComplete(() {
      //裏向きのアニメーション付きカードを消去
      _displayCards.removeLast();
      _cardFlip02();
    });
    setState(() {});
  }
  void _cardFlip02() {
    StageCard lastStageCard = _stageCards.last;
    //表向きのアニメーション付きカードを追加
    if (lastStageCard.cardType == 0) {
      _displayCards.add(
        AnimatedBuilder(
          animation: _animationController,
          builder: (BuildContext context, Widget? child) {
            return Transform.translate(
              offset: _offsetAnimation01.value,
              child: Image.asset(ConstValue.imagesAlphabet[lastStageCard.cardForeIndex])
            );
          }
        )
      );
    } else if (lastStageCard.cardType == 1) {
      _displayCards.add(
        AnimatedBuilder(
          animation: _animationController,
          builder: (BuildContext context, Widget? child) {
            return Transform.translate(
              offset: _offsetAnimation01.value,
              child: Image.asset(ConstValue.imagesAnimal[lastStageCard.cardForeIndex])
            );
          }
        )
      );
    }
    setState(() {});
    _animationController.reverse().whenComplete(() {
      _animationController.reset();
      setState(() {
        _cardState = 1;
        _busyFlag = false;
      });
    });
  }
  void _cardStore() {
    if (_busyFlag) {
      return;
    }
    _busyFlag = true;
    _sound.playSet();
    StageCard lastStageCard = _stageCards.last;
    List<String> cardTypeImage = ConstValue.imagesAlphabet;
    if (lastStageCard.cardType == 0) {
      cardTypeImage = ConstValue.imagesAlphabet;
    } else if (lastStageCard.cardType == 1) {
      cardTypeImage = ConstValue.imagesAnimal;
    }
    Widget lastCard = SizedBox(
      width: _screenWidth / 3,
      child: Padding(
        padding: const EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 0),
        child: Center(child: Image.asset(cardTypeImage[lastStageCard.cardForeIndex])),
      )
    );
    _storeCards.insert(0,lastCard);
    //表向きのアニメーション付きカードを消去
    _stageCards.removeLast();
    _displayCards.removeLast();
    _cardState = 0;
    if (_stageCards.isEmpty) {
      _cardState = 2;
    }
    _busyFlag = false;
    setState(() {});
  }

}

page_state.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-05
///

//現在のページを記録。initStateでタイミングが合わない時にbuild内で一度だけ実行させるために使用。
class PageState {

  static String _currentPage = '';

  static void setCurrentPage(String str) {
    _currentPage = str;
  }

  static String getCurrentPage() {
    return _currentPage;
  }

}

preferences.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///

import 'package:shared_preferences/shared_preferences.dart';

import 'package:grouping/const_value.dart';

//デバイスに情報を保存
class Preferences {

  //----------------------------

  //言語コード
  static Future<void> setLanguageCode(String str) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString(ConstValue.prefLanguageCode, str);
  }
  static Future<String?> getLanguageCode() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String? str = prefs.getString(ConstValue.prefLanguageCode);
    return str;
  }

  //----------------------------

  //人数
  static Future<void> setPeopleNumber(int num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefPeopleNumber, num);
  }
  static Future<int> getPeopleNumber() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefPeopleNumber) ?? 1;
    return num;
  }

  //----------------------------

  //グループ数
  static Future<void> setGroupNumber(int num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefGroupNumber, num);
  }
  static Future<int> getGroupNumber() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefGroupNumber) ?? 1;
    return num;
  }

  //----------------------------

  //カードタイプ
  static Future<void> setCardType(int num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefCardType, num);
  }
  static Future<int> getCardType() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefCardType) ?? 0;
    return num;
  }

  //----------------------------

  //カードをランダム使用
  static Future<void> setCardRandomize(int num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt(ConstValue.prefCardRandomize, num);
  }
  static Future<int> getCardRandomize() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int num = prefs.getInt(ConstValue.prefCardRandomize) ?? 0;
    return num;
  }

  //----------------------------

  //効果音音量
  static Future<void> setSoundVolume(double num) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setDouble(ConstValue.prefSoundVolume, num);
  }
  static Future<double> getSoundVolume() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final double num = prefs.getDouble(ConstValue.prefSoundVolume) ?? 1.0;
    return num;
  }

  //----------------------------

}

setting.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:grouping/const_value.dart';
import 'package:grouping/language_state.dart';
import 'package:grouping/version_state.dart';
import 'package:grouping/game.dart';
import 'package:grouping/ad_mob.dart';
import 'package:grouping/page_state.dart';

class SettingPage extends StatefulWidget {
  //メインページでは SettingPage(game: _game) と渡している。
  //受け取った game は widget.game でアクセスできる。
  final Game game;
  const SettingPage({Key? key, required this.game}) : super(key: key);

  @override
  State<SettingPage> createState() => _SettingPageState();
}

class _SettingPageState extends State<SettingPage> {
  //これら変数はUIへの表示や入力の為に一時的に使用される。
  //適用ボタンによりGameクラスを介してPreferencesクラスでデバイスに値が保存される
  String _languageKey = ''; //言語コード 'en'
  String _languageValue = '';
  bool _cardReset = false;  //カードの初期化
  int _peopleNumber = 2;
  int _groupNumber = 2;
  int _cardType = 0;
  int _cardRandomize = 1;
  double _soundVolume = 1.0;

  final AdMob _adMob = AdMob(); //広告

  //ページ起動時に一度だけ実行される
  @override
  void initState() {
    super.initState();
    _adMob.load();
    _cardReset = false;
    _peopleNumber = widget.game.peopleNumber;
    _groupNumber = widget.game.groupNumber;
    _cardType = widget.game.cardType;
    _cardRandomize = widget.game.cardRandomize;
    _soundVolume = widget.game.soundVolume;
  }
  //ページ終了時に一度だけ実行される
  @override
  void dispose() {
    PageState.setCurrentPage('');
    _adMob.dispose();
    super.dispose();
  }
  //ページ描画
  @override
  Widget build(BuildContext context) {
    //このページが開いたときに一度だけ実行される処理を記述。initStateではタイミングが合わない為。
    if (PageState.getCurrentPage() != 'setting') {
      PageState.setCurrentPage('setting');
      _cardReset = false;
      _peopleNumber = widget.game.peopleNumber;
      _groupNumber = widget.game.groupNumber;
      _cardType = widget.game.cardType;
      _cardRandomize = widget.game.cardRandomize;
      _soundVolume = widget.game.soundVolume;
      setState((){});
      (() async {
        _languageKey = await LanguageState.getLanguageCode();
        _languageValue = ConstValue.languageCode[_languageKey] ?? '';
        setState((){});
      })();
    }
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        //設定キャンセルボタン
        leading: IconButton(
          icon: const Icon(Icons.close),
          onPressed: () {
            Navigator.of(context).pop(false); //falseを返す
          },
        ),
        title: Text(AppLocalizations.of(context)!.setting),
        foregroundColor: const Color.fromRGBO(255,255,255,1),
        backgroundColor: ConstValue.colorSettingAccent,
        actions: [
          //設定OKボタン
          IconButton(
            icon: const Icon(Icons.check),
            onPressed: () async {
              await LanguageState.setLanguageCode(_languageKey);
              widget.game.cardReset = _cardReset;
              widget.game.peopleNumber = _peopleNumber;
              widget.game.groupNumber = _groupNumber;
              widget.game.cardType = _cardType;
              widget.game.cardRandomize = _cardRandomize;
              widget.game.soundVolume = _soundVolume;
              if (!mounted) {
                return;
              }
              Navigator.of(context).pop(true);  //trueを返す
            },
          ),
        ],
      ),
      body: Column(children:[
        Expanded(
          child: GestureDetector(
            onTap: () => FocusScope.of(context).unfocus(),  //背景タップでキーボードを仕舞う
            child: SingleChildScrollView(
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(children: [
                  Padding(
                    padding: const EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 16),
                    child: Row(children:<Widget>[
                      Expanded(
                        child: Text(AppLocalizations.of(context)!.reset,style: const TextStyle(fontSize: 16)),
                      ),
                      Switch(
                        value: _cardReset,
                        onChanged: (bool value) {
                          setState(() {
                            _cardReset = value;
                          });
                        },
                      ),
                    ]),
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 6, left: 16, right: 16, bottom: 6),
                    child: Row(children:[
                      Text(AppLocalizations.of(context)!.numberOfPeople,style: const TextStyle(fontSize: 16)),
                      const Spacer(),
                      SizedBox(
                        width: 170,
                        child: DropdownButton<int>(
                          value: _peopleNumber,
                          onChanged: (int? value) {
                            setState(() {
                              _peopleNumber = value!;
                            });
                          },
                          items: List.generate(25, (index) {
                            return DropdownMenuItem<int>(
                              value: index + 2,
                              child: Text('${index + 2}'),
                            );
                          })
                        )
                      )
                    ])
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 6),
                    child: Row(children:[
                      Text(AppLocalizations.of(context)!.numberOfGroup,style: const TextStyle(fontSize: 16)),
                      const Spacer(),
                      SizedBox(
                        width: 170,
                        child: DropdownButton<int>(
                          value: _groupNumber,
                          onChanged: (int? value) {
                            setState(() {
                              _groupNumber = value!;
                            });
                          },
                          items: List.generate(25, (index) {
                            return DropdownMenuItem<int>(
                              value: index + 2,
                              child: Text('${index + 2}'),
                            );
                          })
                        )
                      )
                    ])
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                    child: Row(children: [
                      Text(AppLocalizations.of(context)!.cardType,style: const TextStyle(fontSize: 16)),
                      const Spacer(),
                    ])
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 6),
                    child: Row(children:[
                      const Spacer(),
                      SizedBox(
                        width: 130,
                        child: RadioListTile(
                          visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity,vertical: VisualDensity.minimumDensity),
                          contentPadding: EdgeInsets.zero,
                          title: const Text('Alphabet'),
                          value: 0,
                          groupValue: _cardType,
                          onChanged: (int? value) {
                            setState(() {
                              _cardType = value!;
                            });
                          },
                        ),
                      ),
                      SizedBox(
                          width: 130,
                          child: RadioListTile(
                            visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity,vertical: VisualDensity.minimumDensity),
                            contentPadding: EdgeInsets.zero,
                            title: const Text('Animal'),
                            value: 1,
                            groupValue: _cardType,
                            onChanged: (int? value) {
                              setState(() {
                                _cardType = value!;
                              });
                            },
                          )
                      )
                    ]),
                  ),
                  _border(),
                  Padding(
                      padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                      child: Row(children: [
                        Text(AppLocalizations.of(context)!.randomGroupNames,style: const TextStyle(fontSize: 16)),
                        const Spacer(),
                      ])
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 6),
                    child: Row(children:[
                      const Spacer(),
                      SizedBox(
                        width: 130,
                        child: RadioListTile(
                          visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity,vertical: VisualDensity.minimumDensity),
                          contentPadding: EdgeInsets.zero,
                          title: const Text('No'),
                          value: 0,
                          groupValue: _cardRandomize,
                          onChanged: (int? value) {
                            setState(() {
                              _cardRandomize = value!;
                            });
                          },
                        ),
                      ),
                      SizedBox(
                        width: 130,
                        child: RadioListTile(
                          visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity,vertical: VisualDensity.minimumDensity),
                          contentPadding: EdgeInsets.zero,
                          title: const Text('Yes'),
                          value: 1,
                          groupValue: _cardRandomize,
                          onChanged: (int? value) {
                            setState(() {
                              _cardRandomize = value!;
                            });
                          },
                        )
                      )
                    ]),
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 18, left: 16, right: 16, bottom: 0),
                    child: Row(children: [
                      Text(AppLocalizations.of(context)!.soundVolume,style: const TextStyle(fontSize: 16)),
                      const Spacer(),
                    ])
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 6),
                    child: Row(children: <Widget>[
                      Text(_soundVolume.toString()),
                      Expanded(
                        child: Slider(
                          value: _soundVolume,
                          min: 0.0,
                          max: 1.0,
                          divisions: 10,
                          onChanged: (double value) {
                            setState(() {
                              _soundVolume = value;
                            });
                          }
                        )
                      )
                    ])
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 18, left: 0, right: 0, bottom: 0),
                    child: Row(children:[
                      const SizedBox(width:16),
                      Text(AppLocalizations.of(context)!.language,
                        style: const TextStyle(
                          fontSize: 16,
                        )
                      ),
                      const Spacer(),
                    ])
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 12, left: 0, right: 0, bottom: 18),
                    child: Table(
                      children: <TableRow>[
                        TableRow(children: <Widget>[
                          _languageTableCell(0),
                          _languageTableCell(1),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(2),
                          _languageTableCell(3),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(4),
                          _languageTableCell(5),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(6),
                          _languageTableCell(7),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(8),
                          _languageTableCell(9),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(10),
                          _languageTableCell(11),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(12),
                          _languageTableCell(13),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(14),
                          _languageTableCell(15),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(16),
                          _languageTableCell(17),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(18),
                          _languageTableCell(19),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(20),
                          _languageTableCell(21),
                        ]),
                        TableRow(children: <Widget>[
                          _languageTableCell(22),
                          _languageTableCell(23),
                        ]),
                      ]
                    ),
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 24, left: 0, right: 0, bottom: 24),
                    child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children:[
                          Text(AppLocalizations.of(context)!.usage1),
                          const SizedBox(height:15),
                          Text(AppLocalizations.of(context)!.usage2),
                          const SizedBox(height:15),
                          Text(AppLocalizations.of(context)!.usage3),
                          const SizedBox(height:15),
                          Text(AppLocalizations.of(context)!.usage4),
                        ]
                    ),
                  ),
                  _border(),
                  Padding(
                    padding: const EdgeInsets.only(top: 24, left: 0, right: 0, bottom: 36),
                    child: SizedBox(
                      child: Text('version  ${VersionState.versionLoad()}',
                        style: const TextStyle(
                          fontSize: 10,
                        ),
                      ),
                    ),
                  ),
                ]),
              ),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(top: 10, left: 0, right: 0, bottom: 0),
          child: SizedBox(
            width: double.infinity,
            child: _adMob.getAdBannerWidget(),
          ),
        ),
      ]),
    );
  }
  //UIの仕切り用ボーダーライン
  Widget _border() {
    return Container(
      decoration: BoxDecoration(
        border: Border(
          top: BorderSide(
            color: Colors.grey.shade300,
            width: 1,
          ),
        ),
      ),
    );
  }
  //言語一覧表示
  TableCell _languageTableCell(int index) {
    return TableCell(
      child: RadioListTile(
        visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity,vertical: VisualDensity.minimumDensity),
        contentPadding: EdgeInsets.zero,
        title: Text(ConstValue.languageCode.values.elementAt(index)),
        value: ConstValue.languageCode.values.elementAt(index),
        groupValue: _languageValue,
        onChanged: (String? value) {
          setState(() {
            _languageValue = value ?? '';
            _languageKey = ConstValue.languageCode.keys.elementAt(index);
          });
        },
      ),
    );
  }

}

stage_card.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///

import 'dart:ui';

class StageCard {
  int id; //0から
  int cardBackIndex;  //カード裏の模様
  int cardType; //カード表のタイプ
  int cardForeIndex;  //カード表の番号
  Offset cardOffset;  //カードの位置

  //constructor
  StageCard(this.id, this.cardBackIndex, this.cardType, this.cardForeIndex, this.cardOffset);

}

version_state.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///

class VersionState {

  static String _version = '';

  //バージョンを記録
  static void versionSave(String str) {
    _version = str;
  }
  //バージョンを返す
  static String versionLoad() {
    return _version;
  }

}

lib/l10n/app_bg.arb

{
	"@@locale":"bg",
	"@locale": {
		"description": "ブルガリア"
	},
  "flipTheCard": "Обърнете картата",
  "nextCard": "Следваща карта",
  "end": "Край",
  "setting": "Настройка",
  "reset": "Нулиране",
  "numberOfPeople": "Брой хора",
  "numberOfGroup": "Номер на групата",
  "cardType": "Вид на карта",
  "randomGroupNames": "Произволни имена на групи",
  "soundVolume": "Сила на звука на звуковия ефект",
  "language": "език",
  "usage1": "Разделете определения брой хора на групи.",
  "usage2": "Разделете до 26 души равномерно в до 26 групи.",
  "usage3": "След като се подготвят на страницата с настройки, участниците се редуват да обръщат картите. Групата ще се покаже.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_cs.arb

{
	"@@locale":"cs",
	"@locale": {
		"description": "チェコ"
	},
  "flipTheCard": "Otočte kartu",
  "nextCard": "Další karta",
  "end": "Konec",
  "setting": "Nastavení",
  "reset": "Resetovat",
  "numberOfPeople": "Počet lidí",
  "numberOfGroup": "Číslo skupiny",
  "cardType": "Typ karty",
  "randomGroupNames": "Náhodné názvy skupin",
  "soundVolume": "Hlasitost zvukového efektu",
  "language": "Jazyk",
  "usage1": "Rozdělte určený počet lidí do skupin.",
  "usage2": "Rozdělte až 26 lidí rovnoměrně do až 26 skupin.",
  "usage3": "Po přípravě na stránce nastavení se účastníci střídají v obracení karet. Zobrazí se skupina.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_da.arb

{
	"@@locale":"da",
	"@locale": {
		"description": "デンマーク"
	},
  "flipTheCard": "Vend kortet",
  "nextCard": "Næste kort",
  "end": "Ende",
  "setting": "Indstilling",
  "reset": "Nulstil",
  "numberOfPeople": "Antal mennesker",
  "numberOfGroup": "Antal gruppe",
  "cardType": "Kort type",
  "randomGroupNames": "Tilfældige gruppenavne",
  "soundVolume": "Lydeffekt lydstyrke",
  "language": "Sprog",
  "usage1": "Opdel det angivne antal personer i grupper.",
  "usage2": "Opdel op til 26 personer jævnt i op til 26 grupper.",
  "usage3": "Efter at have forberedt sig på indstillingssiden skiftes deltagerne til at vende kortene. Gruppen vil blive vist.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_de.arb

{
	"@@locale":"de",
	"@locale": {
		"description": "ドイツ"
	},
  "flipTheCard": "Drehen Sie die Karte um",
  "nextCard": "Nächste Karte",
  "end": "Ende",
  "setting": "Einstellung",
  "reset": "Zurücksetzen",
  "numberOfPeople": "Anzahl der Personen",
  "numberOfGroup": "Anzahl der Gruppe",
  "cardType": "Speicherkarten-Typ",
  "randomGroupNames": "Zufällige Gruppennamen",
  "soundVolume": "Lautstärke des Soundeffekts",
  "language": "Sprache",
  "usage1": "Teilen Sie die angegebene Personenzahl in Gruppen auf.",
  "usage2": "Teilen Sie bis zu 26 Personen gleichmäßig in bis zu 26 Gruppen auf.",
  "usage3": "Nach der Vorbereitung auf der Einstellungsseite drehen die Teilnehmer abwechselnd die Karten um. Die Gruppe wird angezeigt.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_el.arb

{
	"@@locale":"el",
	"@locale": {
		"description": "ギリシャ"
	},
  "flipTheCard": "Γυρίστε την κάρτα",
  "nextCard": "Επόμενη κάρτα",
  "end": "Τέλος",
  "setting": "Σύνθεση",
  "reset": "Επαναφορά",
  "numberOfPeople": "Αριθμός των ανθρώπων",
  "numberOfGroup": "Αριθμός ομάδας",
  "cardType": "Τύπος κάρτας",
  "randomGroupNames": "Τυχαία ονόματα ομάδων",
  "soundVolume": "Ένταση ήχου εφέ",
  "language": "Γλώσσα",
  "usage1": "Χωρίστε τον καθορισμένο αριθμό ατόμων σε ομάδες.",
  "usage2": "Χωρίστε έως και 26 άτομα ομοιόμορφα σε έως και 26 ομάδες.",
  "usage3": "Αφού προετοιμαστούν στη σελίδα ρυθμίσεων, οι συμμετέχοντες αναποδογυρίζουν με τη σειρά τις κάρτες. Η ομάδα θα εμφανιστεί.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_en.arb

{
	"@@locale":"en",
	"@locale": {
		"description": "英語"
	},
	"flipTheCard": "Flip the Card",
	"nextCard": "Next Card",
	"end": "End",
	"setting": "Setting",
	"reset": "Reset",
	"numberOfPeople": "Number of people",
	"numberOfGroup": "Number of group",
	"cardType": "Card type",
	"randomGroupNames": "Random group names",
	"soundVolume": "Sound Effect Volume",
	"language": "Language",
	"usage1": "Divide the specified number of people into groups.",
	"usage2": "Divide up to 26 people evenly into up to 26 groups.",
	"usage3": "After preparing on the settings page, participants take turns flipping over the cards. The group will be displayed.",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_es.arb

{
	"@@locale":"es",
	"@locale": {
		"description": "スペイン"
	},
  "flipTheCard": "Voltear la tarjeta",
  "nextCard": "Siguiente tarjeta",
  "end": "Fin",
  "setting": "Configuración",
  "reset": "Reiniciar",
  "numberOfPeople": "Número de personas",
  "numberOfGroup": "Número de grupo",
  "cardType": "Tipo de tarjeta",
  "randomGroupNames": "Nombres de grupos aleatorios",
  "soundVolume": "Volumen del efecto de sonido",
  "language": "Idioma",
  "usage1": "Divida el número especificado de personas en grupos.",
  "usage2": "Divida hasta 26 personas de manera uniforme en hasta 26 grupos.",
  "usage3": "Después de prepararse en la página de configuración, los participantes se turnan para darle la vuelta a las tarjetas. Se mostrará el grupo.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_et.arb

{
	"@@locale":"et",
	"@locale": {
		"description": "エストニア"
	},
  "flipTheCard": "Pöörake kaarti",
  "nextCard": "Järgmine kaart",
  "end": "Lõpp",
  "setting": "Seadistamine",
  "reset": "Lähtesta",
  "numberOfPeople": "Inimeste arv",
  "numberOfGroup": "Rühma arv",
  "cardType": "Kaardi tüüp",
  "randomGroupNames": "Juhuslikud rühmade nimed",
  "soundVolume": "Heliefekti helitugevus",
  "language": "Keel",
  "usage1": "Jagage määratud arv inimesi rühmadesse.",
  "usage2": "Jagage kuni 26 inimest ühtlaselt kuni 26 rühma.",
  "usage3": "Pärast seadistuste lehel ettevalmistamist lehvivad osalejad kaarte kordamööda. Rühm kuvatakse.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_fi.arb

{
	"@@locale":"fi",
	"@locale": {
		"description": "フィンランド"
	},
  "flipTheCard": "Käännä kortti",
  "nextCard": "Seuraava kortti",
  "end": "Loppu",
  "setting": "Asetus",
  "reset": "Nollaa",
  "numberOfPeople": "Henkilöiden määrä",
  "numberOfGroup": "Ryhmän lukumäärä",
  "cardType": "Korttityyppi",
  "randomGroupNames": "Satunnaiset ryhmien nimet",
  "soundVolume": "Äänitehosteen äänenvoimakkuus",
  "language": "Kieli",
  "usage1": "Jaa määritetty määrä ihmisiä ryhmiin.",
  "usage2": "Jaa jopa 26 henkilöä tasaisesti jopa 26 ryhmään.",
  "usage3": "Valmistuttuaan asetussivulla osallistujat kääntävät kortit vuorotellen. Ryhmä tulee näkyviin.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_fr.arb

{
	"@@locale":"fr",
	"@locale": {
		"description": "フランス"
	},
  "flipTheCard": "Retournez la carte",
  "nextCard": "Carte suivante",
  "end": "Fin",
  "setting": "Paramètre",
  "reset": "Réinitialiser",
  "numberOfPeople": "Nombre de personnes",
  "numberOfGroup": "Nombre de groupe",
  "cardType": "Type de carte",
  "randomGroupNames": "Noms de groupes aléatoires",
  "soundVolume": "Volume des effets sonores",
  "language": "Langue",
  "usage1": "Divisez le nombre spécifié de personnes en groupes.",
  "usage2": "Divisez jusqu'à 26 personnes uniformément en 26 groupes maximum.",
  "usage3": "Après s'être préparés sur la page des paramètres, les participants retournent les cartes à tour de rôle. Le groupe sera affiché.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_hu.arb

{
	"@@locale":"hu",
	"@locale": {
		"description": "ハンガリー"
	},
  "flipTheCard": "Fordítsa meg a kártyát",
  "nextCard": "Következő kártya",
  "end": "Vége",
  "setting": "Beállítás",
  "reset": "Visszaállítás",
  "numberOfPeople": "Emberek száma",
  "numberOfGroup": "Csoport száma",
  "cardType": "Kártyatípus",
  "randomGroupNames": "Véletlenszerű csoportnevek",
  "soundVolume": "Hangeffektus hangereje",
  "language": "Nyelv",
  "usage1": "Osszuk csoportokra a megadott számú embert.",
  "usage2": "Akár 26 embert egyenletesen osszon legfeljebb 26 csoportra.",
  "usage3": "A beállítások oldalon való felkészülés után a résztvevők felváltva lapozgatják a kártyákat. Megjelenik a csoport.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_it.arb

{
	"@@locale":"it",
	"@locale": {
		"description": "イタリア"
	},
  "flipTheCard": "Gira la carta",
  "nextCard": "Carta successiva",
  "end": "FINE",
  "setting": "Collocamento",
  "reset": "Ripristina",
  "numberOfPeople": "Numero di persone",
  "numberOfGroup": "Numero di gruppo",
  "cardType": "Tipo di carta",
  "randomGroupNames": "Nomi di gruppi casuali",
  "soundVolume": "Volume dell'effetto sonoro",
  "language": "Lingua",
  "usage1": "Dividere il numero specificato di persone in gruppi.",
  "usage2": "Dividere equamente fino a 26 persone in un massimo di 26 gruppi.",
  "usage3": "Dopo essersi preparati nella pagina delle impostazioni, i partecipanti girano a turno le carte. Verrà visualizzato il gruppo.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_ja.arb

{
	"@@locale":"ja",
	"@locale": {
		"description": "日本"
	},
	"flipTheCard": "カードをめくる",
	"nextCard": "次のカード",
	"end": "終わり",
	"setting": "設定",
	"reset": "リセット",
	"numberOfPeople": "人数",
	"numberOfGroup": "グループ数",
	"cardType": "カードタイプ",
	"randomGroupNames": "グループ名はランダム",
	"soundVolume": "効果音の音量",
	"language": "言語",
	"usage1": "指定の人数をグループ分けします。",
	"usage2": "最大26人を最大26グループに均等に分けます。",
	"usage3": "設定ページで準備後、参加者が順にカードをめくります。グループが表示されます。",
	"usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_lt.arb

{
	"@@locale":"lt",
	"@locale": {
		"description": "リトアニア"
	},
  "flipTheCard": "Apverskite kortelę",
  "nextCard": "Kita kortelė",
  "end": "Galas",
  "setting": "Nustatymas",
  "reset": "Nustatyti iš naujo",
  "numberOfPeople": "Žmonių skaičius",
  "numberOfGroup": "Grupės skaičius",
  "cardType": "Kortelės tipas",
  "randomGroupNames": "Atsitiktiniai grupių pavadinimai",
  "soundVolume": "Garso efekto garsumas",
  "language": "Kalba",
  "usage1": "Suskirstykite nurodytą skaičių žmonių į grupes.",
  "usage2": "Padalinkite iki 26 žmonių tolygiai į 26 grupes.",
  "usage3": "Pasiruošę nustatymų puslapyje, dalyviai paeiliui apverčia korteles. Bus rodoma grupė.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_lv.arb

{
	"@@locale":"lv",
	"@locale": {
		"description": "ラトビア"
	},
  "flipTheCard": "Apgrieziet karti",
  "nextCard": "Nākamā karte",
  "end": "Beigas",
  "setting": "Iestatījums",
  "reset": "Atiestatīt",
  "numberOfPeople": "Cilvēku skaits",
  "numberOfGroup": "Grupas skaits",
  "cardType": "Kartes veids",
  "randomGroupNames": "Nejauši grupu nosaukumi",
  "soundVolume": "Skaņas efekta skaļums",
  "language": "Valoda",
  "usage1": "Sadaliet norādīto cilvēku skaitu grupās.",
  "usage2": "Vienmērīgi sadaliet līdz 26 cilvēkiem līdz 26 grupās.",
  "usage3": "Pēc sagatavošanās iestatījumu lapā dalībnieki pārmaiņus pārvērš kārtis. Tiks parādīta grupa.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_nl.arb

{
	"@@locale":"nl",
	"@locale": {
		"description": "オランダ"
	},
  "flipTheCard": "Draai de kaart om",
  "nextCard": "Volgende kaart",
  "end": "Einde",
  "setting": "Instelling",
  "reset": "Opnieuw instellen",
  "numberOfPeople": "Aantal mensen",
  "numberOfGroup": "Aantal groep",
  "cardType": "Kaarttype",
  "randomGroupNames": "Willekeurige groepsnamen",
  "soundVolume": "Geluidseffectvolume",
  "language": "Taal",
  "usage1": "Verdeel het opgegeven aantal mensen in groepen.",
  "usage2": "Verdeel maximaal 26 personen gelijkmatig in maximaal 26 groepen.",
  "usage3": "Na voorbereiding op de instellingenpagina draaien de deelnemers om de beurt de kaarten om. De groep wordt weergegeven.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_pl.arb

{
	"@@locale":"pl",
	"@locale": {
		"description": "ポーランド"
	},
  "flipTheCard": "Odwróć kartę",
  "nextCard": "Następna karta",
  "end": "Koniec",
  "setting": "Ustawienie",
  "reset": "Resetowanie",
  "numberOfPeople": "Liczba ludzi",
  "numberOfGroup": "Liczba grup",
  "cardType": "Typ karty",
  "randomGroupNames": "Losowe nazwy grup",
  "soundVolume": "Głośność efektu dźwiękowego",
  "language": "Język",
  "usage1": "Podziel określoną liczbę osób na grupy.",
  "usage2": "Podziel maksymalnie 26 osób równomiernie na maksymalnie 26 grup.",
  "usage3": "Po przygotowaniu na stronie ustawień uczestnicy na zmianę odwracają karty. Grupa zostanie wyświetlona.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_pt.arb

{
	"@@locale":"pt",
	"@locale": {
		"description": "ポルトガル"
	},
  "flipTheCard": "Vire o cartão",
  "nextCard": "Próximo cartão",
  "end": "Fim",
  "setting": "Contexto",
  "reset": "Reiniciar",
  "numberOfPeople": "Número de pessoas",
  "numberOfGroup": "Número do grupo",
  "cardType": "Tipo de carta",
  "randomGroupNames": "Nomes de grupos aleatórios",
  "soundVolume": "Volume do efeito sonoro",
  "language": "Linguagem",
  "usage1": "Divida o número especificado de pessoas em grupos.",
  "usage2": "Divida até 26 pessoas igualmente em até 26 grupos.",
  "usage3": "Após a preparação na página de configurações, os participantes se revezam para virar os cartões. O grupo será exibido.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_ro.arb

{
	"@@locale":"ro",
	"@locale": {
		"description": "ルーマニア"
	},
  "flipTheCard": "Întoarce cardul",
  "nextCard": "Următorul card",
  "end": "Sfârşit",
  "setting": "Setare",
  "reset": "Resetați",
  "numberOfPeople": "Numărul de persoane",
  "numberOfGroup": "Numărul grupului",
  "cardType": "Tipul cardului",
  "randomGroupNames": "Nume aleatorii ale grupurilor",
  "soundVolume": "Volumul efectului sonor",
  "language": "Limba",
  "usage1": "Împărțiți numărul specificat de persoane în grupuri.",
  "usage2": "Împărțiți până la 26 de persoane în mod egal în până la 26 de grupuri.",
  "usage3": "După ce s-au pregătit pe pagina de setări, participanții răstoarnă pe rând cărțile. Grupul va fi afișat.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_ru.arb

{
	"@@locale":"ru",
	"@locale": {
		"description": "ロシア"
	},
  "flipTheCard": "Переверните карту",
  "nextCard": "Следующая карта",
  "end": "Конец",
  "setting": "Параметр",
  "reset": "Перезагрузить",
  "numberOfPeople": "Число людей",
  "numberOfGroup": "Номер группы",
  "cardType": "Тип карты",
  "randomGroupNames": "Случайные названия групп",
  "soundVolume": "Громкость звукового эффекта",
  "language": "Язык",
  "usage1": "Разделите указанное количество людей на группы.",
  "usage2": "Разделите до 26 человек равномерно на 26 групп.",
  "usage3": "После подготовки на странице настроек участники по очереди переворачивают карточки. Группа будет отображена.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_sk.arb

{
	"@@locale":"sk",
	"@locale": {
		"description": "スロバキア"
	},
  "flipTheCard": "Otočte kartu",
  "nextCard": "Ďalšia karta",
  "end": "Koniec",
  "setting": "Nastavenie",
  "reset": "Resetovať",
  "numberOfPeople": "Počet ľudí",
  "numberOfGroup": "Číslo skupiny",
  "cardType": "Typ karty",
  "randomGroupNames": "Náhodné názvy skupín",
  "soundVolume": "Hlasitosť zvukových efektov",
  "language": "Jazyk",
  "usage1": "Rozdeľte určený počet ľudí do skupín.",
  "usage2": "Rozdeľte až 26 ľudí rovnomerne do až 26 skupín.",
  "usage3": "Po príprave na stránke nastavení sa účastníci striedavo obracajú kartami. Zobrazí sa skupina.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_sv.arb

{
	"@@locale":"sv",
	"@locale": {
		"description": "スウェーデン"
	},
  "flipTheCard": "Vänd på kortet",
  "nextCard": "Nästa kort",
  "end": "Slutet",
  "setting": "Miljö",
  "reset": "Återställa",
  "numberOfPeople": "Antal personer",
  "numberOfGroup": "Antal grupp",
  "cardType": "Kort typ",
  "randomGroupNames": "Slumpmässiga gruppnamn",
  "soundVolume": "Ljudeffektvolym",
  "language": "Språk",
  "usage1": "Dela upp det angivna antalet personer i grupper.",
  "usage2": "Dela upp till 26 personer jämnt i upp till 26 grupper.",
  "usage3": "Efter att ha förberett sig på inställningssidan turas deltagarna om att vända på korten. Gruppen kommer att visas.",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_th.arb

{
	"@@locale":"th",
	"@locale": {
		"description": "タイ"
	},
  "flipTheCard": "พลิกการ์ด",
  "nextCard": "การ์ดถัดไป",
  "end": "จบ",
  "setting": "การตั้งค่า",
  "reset": "รีเซ็ต",
  "numberOfPeople": "จำนวนคน",
  "numberOfGroup": "จำนวนกลุ่ม",
  "cardType": "ประเภทบัตร",
  "randomGroupNames": "สุ่มชื่อกลุ่ม",
  "soundVolume": "ระดับเสียงเอฟเฟกต์",
  "language": "ภาษา",
  "usage1": "แบ่งจำนวนคนที่ระบุออกเป็นกลุ่ม",
  "usage2": "แบ่งคนได้มากถึง 26 คนเท่าๆ กันออกเป็น 26 กลุ่ม",
  "usage3": "หลังจากเตรียมตัวในหน้าการตั้งค่าแล้ว ผู้เข้าร่วมจะผลัดกันพลิกการ์ด กลุ่มจะปรากฏขึ้น",
  "usage4": "",

	"dummy": "dummy"
}

lib/l10n/app_zh.arb

{
	"@@locale":"zh",
	"@locale": {
		"description": "中国"
	},
  "flipTheCard": "翻转卡片",
  "nextCard": "下一张卡",
  "end": "结尾",
  "setting": "环境",
  "reset": "重置",
  "numberOfPeople": "人数",
  "numberOfGroup": "组数",
  "cardType": "卡的种类",
  "randomGroupNames": "随机组名",
  "soundVolume": "音效音量",
  "language": "语言",
  "usage1": "将指定数量的人分成几组。",
  "usage2": "将最多 26 人平均分成最多 26 组。",
  "usage3": "在设置页面上做好准备后,参与者轮流翻动卡片。 将显示该组。",
  "usage4": "",

	"dummy": "dummy"
}
このページでお気付きの点がございましたら
ご連絡いただけますと幸いです。
Eメール(任意)
送信内容
 

便利ウェブサイト