.NET & C++ Localizer Skill
.NETとC++アプリケーションの国際化(i18n)と地域化(l10n)を完全サポートするスキルです。
概要
このスキルは、.NET(.resx)とC++(gettext/ICU)の両方に対応した、エンタープライズレベルの国際化ソリューションを提供します。リソースファイル生成、翻訳管理、ベストプラクティス適用まで、国際化のすべてをカバーします。
主な機能
-
🎯 .NET サポート: .resx、IStringLocalizer、ASP.NET Core localization
-
🎯 C++ サポート: gettext (.po/.pot)、ICU、Boost.Locale
-
📦 リソース生成: 自動リソースファイル作成
-
🔍 文字列抽出: ハードコードされた文字列の自動検出
-
🌐 翻訳管理: 複数言語の翻訳ファイル管理
-
✅ 品質チェック: 翻訳漏れ、重複、フォーマットエラー検出
-
📊 進捗トラッキング: 言語別の翻訳完了率
-
🔄 同期ツール: コード変更への自動追従
-
🎨 複数形対応: .NET plural rules、gettext ngettext
-
📱 文化対応: 日付・通貨・数値のロケール別フォーマット
サポート技術
.NET ファミリー
-
.NET Core / .NET 6/7/8/9
-
ASP.NET Core (MVC, Razor Pages, Blazor)
-
WPF (Windows Presentation Foundation)
-
WinForms (Windows Forms)
-
Xamarin (iOS/Android)
-
.NET MAUI (Multi-platform App UI)
-
Unity (C# ゲームエンジン)
C++ フレームワーク
-
gettext - GNU翻訳システム(最も人気)
-
ICU - International Components for Unicode
-
Boost.Locale - C++標準ライブラリ風API
-
Qt Linguist - Qt フレームワーク (.ts ファイル)
-
wxWidgets - クロスプラットフォームGUI
使用方法
.NET アプリケーションの国際化
基本的なリソースファイル生成
.NET Coreプロジェクトに国際化を追加:
プロジェクトタイプ: ASP.NET Core MVC 言語: 英語(デフォルト)、日本語、中国語、スペイン語 リソース場所: Resources/ 文化: ja-JP, zh-CN, es-ES
タスク:
- .resx ファイル生成
- IStringLocalizer 設定
- Startup.cs 設定
- ハードコード文字列の抽出と置き換え
WPF アプリケーション
WPF デスクトップアプリに多言語対応:
言語: 英語、日本語、ドイツ語 リソース: Properties/Resources.resx UI更新: すべてのラベル、ボタン、メッセージボックス 言語切り替え: ランタイムで切り替え可能
C++ アプリケーションの国際化
gettext 統合
C++ コンソールアプリケーションにgettextを統合:
ライブラリ: GNU gettext 言語: 英語、日本語、フランス語 ドメイン: myapp 出力: po/ja/myapp.po, po/fr/myapp.po
タスク:
- gettext セットアップコード生成
- 文字列抽出(xgettext)
- .pot/.po ファイル生成
- コード修正(_() マクロ)
ICU 統合
C++ プロジェクトにICUを統合:
機能:
- Unicode文字列処理
- 日付・時刻フォーマット(ロケール別)
- 数値・通貨フォーマット
- 複数形ルール
- ソート・検索(ロケール対応)
.NET 実装パターン
- ASP.NET Core MVC の国際化
生成されるファイル構成:
MyWebApp/ ├── Resources/ │ ├── Controllers/ │ │ ├── HomeController.en.resx │ │ ├── HomeController.ja.resx │ │ └── HomeController.zh.resx │ ├── Views/ │ │ ├── Home/ │ │ │ ├── Index.en.resx │ │ │ ├── Index.ja.resx │ │ │ └── Index.zh.resx │ │ └── Shared/ │ │ ├── _Layout.en.resx │ │ └── _Layout.ja.resx │ └── SharedResources.resx ├── Program.cs (または Startup.cs) └── appsettings.json
Program.cs (.NET 6+):
using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.Options; using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
// Localization サービス追加 builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.AddControllersWithViews() .AddViewLocalization() .AddDataAnnotationsLocalization();
// サポートする文化を設定 var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("ja"), new CultureInfo("zh"), new CultureInfo("es") };
builder.Services.Configure<RequestLocalizationOptions>(options => { options.DefaultRequestCulture = new RequestCulture("en"); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures;
// クッキーベースの文化選択
options.RequestCultureProviders.Insert(0, new CookieRequestCultureProvider());
});
var app = builder.Build();
// Localization ミドルウェア追加(重要: UseRouting より前) app.UseRequestLocalization( app.Services.GetRequiredService<IOptions<RequestLocalizationOptions>>().Value );
app.UseRouting(); app.UseAuthorization();
app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Resources/Controllers/HomeController.ja.resx:
<?xml version="1.0" encoding="utf-8"?> <root> <data name="Welcome" xml:space="preserve"> <value>ようこそ</value> </data> <data name="HelloMessage" xml:space="preserve"> <value>こんにちは、{0}さん!</value> </data> <data name="ItemCount" xml:space="preserve"> <value>{0} 件のアイテム</value> </data> </root>
HomeController.cs:
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization;
public class HomeController : Controller { private readonly IStringLocalizer<HomeController> _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
// リソースから文字列取得
ViewData["Message"] = _localizer["Welcome"];
// パラメータ付き
var userName = "田中";
ViewData["Greeting"] = _localizer["HelloMessage", userName];
// 複数形対応
var count = 5;
ViewData["Items"] = _localizer["ItemCount", count];
return View();
}
[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
return LocalRedirect(returnUrl);
}
}
Views/Home/Index.cshtml:
@using Microsoft.AspNetCore.Mvc.Localization @inject IViewLocalizer Localizer
<h1>@Localizer["Welcome"]</h1> <p>@ViewData["Greeting"]</p> <p>@ViewData["Items"]</p>
<!-- 言語切り替え --> <form asp-action="SetLanguage" asp-controller="Home" method="post"> <input type="hidden" name="returnUrl" value="@Context.Request.Path" /> <select name="culture" onchange="this.form.submit()"> <option value="en">English</option> <option value="ja">日本語</option> <option value="zh">中文</option> <option value="es">Español</option> </select> </form>
- WPF デスクトップアプリケーション
App.xaml.cs:
using System.Globalization; using System.Threading; using System.Windows;
public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e);
// ユーザー設定から言語読み込み
var cultureName = Properties.Settings.Default.Language ?? "en";
SetLanguage(cultureName);
}
public static void SetLanguage(string cultureName)
{
var culture = new CultureInfo(cultureName);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
// WPFのリソースディクショナリを更新
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
}
}
MainWindow.xaml.cs:
using System.Windows;
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); UpdateUI(); }
private void UpdateUI()
{
// リソースから文字列取得
Title = Properties.Resources.AppTitle;
WelcomeLabel.Content = Properties.Resources.Welcome;
LoginButton.Content = Properties.Resources.Login;
}
private void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (LanguageComboBox.SelectedItem is ComboBoxItem item)
{
var culture = item.Tag.ToString();
App.SetLanguage(culture);
// 設定を保存
Properties.Settings.Default.Language = culture;
Properties.Settings.Default.Save();
// UIを更新(または再起動)
UpdateUI();
}
}
}
Properties/Resources.resx (英語):
<data name="AppTitle" xml:space="preserve"> <value>My Application</value> </data> <data name="Welcome" xml:space="preserve"> <value>Welcome!</value> </data> <data name="Login" xml:space="preserve"> <value>Login</value> </data>
Properties/Resources.ja.resx (日本語):
<data name="AppTitle" xml:space="preserve"> <value>私のアプリケーション</value> </data> <data name="Welcome" xml:space="preserve"> <value>ようこそ!</value> </data> <data name="Login" xml:space="preserve"> <value>ログイン</value> </data>
- .NET MAUI (マルチプラットフォーム)
MauiProgram.cs:
using Microsoft.Extensions.Localization; using System.Globalization;
public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); });
// Localization追加
builder.Services.AddLocalization();
return builder.Build();
}
}
Resources/Strings/AppResources.ja.resx:
<data name="Welcome" xml:space="preserve"> <value>ようこそ</value> </data>
MainPage.xaml.cs:
using Microsoft.Extensions.Localization;
public partial class MainPage : ContentPage { private readonly IStringLocalizer<AppResources> _localizer;
public MainPage(IStringLocalizer<AppResources> localizer)
{
InitializeComponent();
_localizer = localizer;
WelcomeLabel.Text = _localizer["Welcome"];
}
}
C++ 実装パターン
- gettext による国際化
ファイル構成:
myapp/ ├── src/ │ ├── main.cpp │ └── i18n.h ├── po/ │ ├── myapp.pot # テンプレート │ ├── ja/ │ │ └── myapp.po # 日本語翻訳 │ ├── fr/ │ │ └── myapp.po # フランス語翻訳 │ └── de/ │ └── myapp.po # ドイツ語翻訳 ├── locale/ # コンパイル済み .mo ファイル │ ├── ja/ │ │ └── LC_MESSAGES/ │ │ └── myapp.mo │ └── fr/ │ └── LC_MESSAGES/ │ └── myapp.mo └── CMakeLists.txt
i18n.h (ヘッダー):
#ifndef I18N_H #define I18N_H
#include <libintl.h> #include <locale.h>
// 翻訳マクロ #define (STRING) gettext(STRING) #define N(STRING) STRING
// 複数形対応 #define _n(SINGULAR, PLURAL, N) ngettext(SINGULAR, PLURAL, N)
// 初期化関数 inline void initI18n(const char* domain, const char* locale_dir) { // ロケール設定 setlocale(LC_ALL, "");
// gettext設定
bindtextdomain(domain, locale_dir);
bind_textdomain_codeset(domain, "UTF-8");
textdomain(domain);
}
#endif // I18N_H
main.cpp:
#include <iostream> #include <string> #include "i18n.h"
int main(int argc, char* argv[]) { // 国際化初期化 initI18n("myapp", "./locale");
// 翻訳された文字列を使用
std::cout << _("Welcome to My Application!") << std::endl;
std::cout << _("Please enter your name: ");
std::string name;
std::getline(std::cin, name);
// パラメータ付き(printfスタイル)
printf(_("Hello, %s!\n"), name.c_str());
// 複数形対応
int count = 5;
printf(_n("You have %d new message",
"You have %d new messages",
count),
count);
std::cout << std::endl;
// メニュー表示
std::cout << "\n" << _("Menu:") << std::endl;
std::cout << "1. " << _("View Profile") << std::endl;
std::cout << "2. " << _("Settings") << std::endl;
std::cout << "3. " << _("Exit") << std::endl;
return 0;
}
翻訳ファイル生成手順:
1. 翻訳可能な文字列を抽出 (.pot ファイル生成)
xgettext --keyword=_ --keyword=_n:1,2 --language=C++
--add-comments --sort-output
--output=po/myapp.pot
src/*.cpp
2. 各言語の .po ファイル作成(初回のみ)
msginit --input=po/myapp.pot --locale=ja_JP --output=po/ja/myapp.po msginit --input=po/myapp.pot --locale=fr_FR --output=po/fr/myapp.po
3. .po ファイルを編集(翻訳者が実施)
例: po/ja/myapp.po
4. 既存の .po ファイルを更新(コード変更時)
msgmerge --update po/ja/myapp.po po/myapp.pot
5. .mo ファイルにコンパイル
msgfmt --output-file=locale/ja/LC_MESSAGES/myapp.mo po/ja/myapp.po msgfmt --output-file=locale/fr/LC_MESSAGES/myapp.mo po/fr/myapp.po
po/ja/myapp.po (日本語翻訳例):
Japanese translation for myapp
msgid "" msgstr "" "Project-Id-Version: myapp 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Language: ja\n"
msgid "Welcome to My Application!" msgstr "私のアプリケーションへようこそ!"
msgid "Please enter your name: " msgstr "お名前を入力してください: "
msgid "Hello, %s!\n" msgstr "こんにちは、%sさん!\n"
複数形
msgid "You have %d new message" msgid_plural "You have %d new messages" msgstr[0] "%d 件の新しいメッセージがあります"
msgid "Menu:" msgstr "メニュー:"
msgid "View Profile" msgstr "プロフィールを表示"
msgid "Settings" msgstr "設定"
msgid "Exit" msgstr "終了"
CMakeLists.txt (ビルド統合):
cmake_minimum_required(VERSION 3.10) project(myapp)
set(CMAKE_CXX_STANDARD 17)
gettext ライブラリを探す
find_package(Gettext REQUIRED) find_package(Intl REQUIRED)
実行ファイル
add_executable(myapp src/main.cpp)
インクルードディレクトリ
target_include_directories(myapp PRIVATE ${Intl_INCLUDE_DIRS})
ライブラリリンク
target_link_libraries(myapp PRIVATE ${Intl_LIBRARIES})
.po ファイルから .mo ファイルを生成
set(LANGUAGES ja fr de) set(PO_DIR ${CMAKE_SOURCE_DIR}/po) set(LOCALE_DIR ${CMAKE_BINARY_DIR}/locale)
foreach(LANG ${LANGUAGES}) set(PO_FILE ${PO_DIR}/${LANG}/myapp.po) set(MO_DIR ${LOCALE_DIR}/${LANG}/LC_MESSAGES) set(MO_FILE ${MO_DIR}/myapp.mo)
# ディレクトリ作成
file(MAKE_DIRECTORY ${MO_DIR})
# カスタムコマンド: .po → .mo
add_custom_command(
OUTPUT ${MO_FILE}
COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${MO_FILE} ${PO_FILE}
DEPENDS ${PO_FILE}
COMMENT "Compiling ${LANG} translation"
)
list(APPEND MO_FILES ${MO_FILE})
endforeach()
すべての .mo ファイルをビルド
add_custom_target(translations ALL DEPENDS ${MO_FILES}) add_dependencies(myapp translations)
インストール
install(DIRECTORY ${LOCALE_DIR}/ DESTINATION share/locale)
- ICU による高度な国際化
ICU 使用例:
#include <iostream> #include <unicode/ucnv.h> #include <unicode/unistr.h> #include <unicode/datefmt.h> #include <unicode/numfmt.h> #include <unicode/plurrule.h> #include <unicode/msgfmt.h>
using namespace icu;
int main() { UErrorCode status = U_ZERO_ERROR;
// 1. Unicode文字列処理
UnicodeString text("Hello, 世界! 你好!");
std::cout << "Length: " << text.length() << " characters" << std::endl;
// 2. 日付フォーマット(ロケール別)
Locale localeJa("ja_JP");
Locale localeEn("en_US");
DateFormat* dateFormatJa = DateFormat::createDateInstance(
DateFormat::kFull, localeJa);
DateFormat* dateFormatEn = DateFormat::createDateInstance(
DateFormat::kFull, localeEn);
UDate now = Calendar::getNow();
UnicodeString dateJa, dateEn;
dateFormatJa->format(now, dateJa);
dateFormatEn->format(now, dateEn);
std::string dateJaUtf8, dateEnUtf8;
dateJa.toUTF8String(dateJaUtf8);
dateEn.toUTF8String(dateEnUtf8);
std::cout << "Date (ja): " << dateJaUtf8 << std::endl;
std::cout << "Date (en): " << dateEnUtf8 << std::endl;
// 3. 数値フォーマット(通貨)
NumberFormat* currencyJa = NumberFormat::createCurrencyInstance(
localeJa, status);
NumberFormat* currencyEn = NumberFormat::createCurrencyInstance(
localeEn, status);
double amount = 1234567.89;
UnicodeString currencyJaStr, currencyEnStr;
currencyJa->format(amount, currencyJaStr);
currencyEn->format(amount, currencyEnStr);
std::string currencyJaUtf8, currencyEnUtf8;
currencyJaStr.toUTF8String(currencyJaUtf8);
currencyEnStr.toUTF8String(currencyEnUtf8);
std::cout << "Currency (ja): " << currencyJaUtf8 << std::endl;
std::cout << "Currency (en): " << currencyEnUtf8 << std::endl;
// 4. 複数形ルール
PluralRules* pluralRulesEn = PluralRules::forLocale(localeEn, status);
for (int i = 0; i <= 5; i++) {
UnicodeString keyword = pluralRulesEn->select(i);
std::string keywordUtf8;
keyword.toUTF8String(keywordUtf8);
std::cout << i << " -> " << keywordUtf8 << std::endl;
}
// 5. MessageFormat (パラメータ付きメッセージ)
UnicodeString pattern("{0} has {1, plural, "
"one {# apple} "
"other {# apples}}.");
MessageFormat msgFormat(pattern, localeEn, status);
UnicodeString name("Alice");
int appleCount = 3;
Formattable args[] = { name, appleCount };
UnicodeString result;
msgFormat.format(args, 2, result, status);
std::string resultUtf8;
result.toUTF8String(resultUtf8);
std::cout << "Message: " << resultUtf8 << std::endl;
// クリーンアップ
delete dateFormatJa;
delete dateFormatEn;
delete currencyJa;
delete currencyEn;
delete pluralRulesEn;
return 0;
}
CMakeLists.txt (ICU):
cmake_minimum_required(VERSION 3.10) project(icu_example)
set(CMAKE_CXX_STANDARD 17)
ICU を探す
find_package(ICU REQUIRED COMPONENTS uc i18n)
add_executable(icu_example main.cpp)
target_include_directories(icu_example PRIVATE ${ICU_INCLUDE_DIRS}) target_link_libraries(icu_example PRIVATE ${ICU_LIBRARIES})
- Boost.Locale (C++標準風API)
#include <boost/locale.hpp> #include <iostream>
using namespace boost::locale;
int main() { // ロケールジェネレーター generator gen;
// サポートロケール追加
gen.add_messages_path("./locale");
gen.add_messages_domain("myapp");
// ロケール設定
std::locale::global(gen("ja_JP.UTF-8"));
std::cout.imbue(std::locale());
// 翻訳
std::cout << translate("Welcome!") << std::endl;
std::cout << translate("Hello, {1}!").str("田中") << std::endl;
// 複数形
std::cout << format(translate("You have {1} message",
"You have {1} messages",
5)) % 5
<< std::endl;
// 日付フォーマット
auto now = std::time(nullptr);
std::cout << as::date << std::put_time(std::localtime(&now), "%x")
<< std::endl;
// 通貨
std::cout << as::currency << 1234.56 << std::endl;
return 0;
}
ベストプラクティス
.NET
-
IStringLocalizer 使用: ResourceManager より推奨
-
リソース配置: コントローラー/ビュー別に整理
-
ハードコード回避: すべての文字列をリソース化
-
文化フォールバック: ja-JP → ja → デフォルト
-
キャッシング: パフォーマンス向上
-
データアノテーション: バリデーションメッセージも国際化
C++
-
gettext 優先: 翻訳管理に最適
-
ICU 併用: 日付・通貨・複数形はICU
-
UTF-8 統一: すべての文字列をUTF-8
-
マクロ使用: _() でシンプルに
-
コンテキスト: pgettext でキー重複回避
-
自動抽出: xgettext で翻訳文字列抽出
翻訳ワークフロー
- 文字列抽出
.NET:
カスタムツールで .resx から未翻訳キーを抽出
dotnet run --project LocalizationTool extract --output missing.csv
C++:
xgettext で翻訳可能文字列を抽出
xgettext --keyword=_ --output=messages.pot src/**/*.cpp
- 翻訳
-
手動翻訳: Poedit、Lokalize などのGUIツール
-
機械翻訳: DeepL/Google Translate API統合
-
翻訳サービス: Crowdin、Lokalise、Phrase
- 品質チェック
.NET: 重複キー検出
dotnet run --project LocalizationTool validate
C++: .po ファイル検証
msgfmt --check-format --check-header ja.po
- 進捗レポート
Translation Progress
Japanese (ja): 100% ✅ (250/250) Chinese (zh): 85% ⚠️ (213/250) Spanish (es): 60% ❌ (150/250)
Missing in Chinese:
- Settings.Privacy.DeleteAccount
- Checkout.Payment.ApplePay ...
高度な機能
複数形ルール
.NET (.resx):
<data name="ItemCount" xml:space="preserve"> <value>{0} items</value> </data>
C++ (gettext):
msgid "%d item" msgid_plural "%d items" msgstr[0] "%d 個のアイテム" # 日本語は単複同形
日付・通貨フォーマット
.NET:
var date = DateTime.Now; var formattedDate = date.ToString("D", CultureInfo.CurrentCulture);
var price = 1234.56m; var formattedPrice = price.ToString("C", CultureInfo.CurrentCulture); // ja-JP: ¥1,235 // en-US: $1,234.56
C++ (ICU):
NumberFormat* fmt = NumberFormat::createCurrencyInstance(Locale("ja_JP"), status); UnicodeString result; fmt->format(1234.56, result); // ¥1,235
ツールとユーティリティ
.NET ツール
-
ResXManager - Visual Studio拡張
-
Zeta Resource Editor - スタンドアロンエディター
-
Lokalise.NET - APIクライアント
C++ ツール
-
Poedit - .po ファイルエディター(GUI)
-
gettext utilities - xgettext, msgfmt, msgmerge
-
translate-toolkit - Pythonベース変換ツール
統合例
例1: ASP.NET Core Web API
ASP.NET Core Web APIに多言語対応:
- API レスポンスメッセージ
- バリデーションエラー
- Accept-Language ヘッダー対応 言語: 英語、日本語、中国語
例2: WPF デスクトップアプリ
WPF 在庫管理アプリを多言語化:
- すべてのUI要素
- メッセージボックス
- レポート出力
- ランタイム言語切り替え 言語: 英語、日本語、ドイツ語、スペイン語
例3: C++ クロスプラットフォームCLI
C++ コマンドラインツールにgettext統合:
- ヘルプメッセージ
- エラーメッセージ
- 進捗表示
- 環境変数でロケール切り替え 言語: 英語、日本語、フランス語
制限事項
-
.NET: リソースファイルはコンパイル時埋め込み(動的追加不可)
-
C++: gettext はランタイムで .mo ファイル必要
-
ICU: ライブラリサイズが大きい(30MB+)
-
複数形: 言語により複数形ルールが異なる
バージョン情報
-
スキルバージョン: 1.0.0
-
対応 .NET: .NET Core 3.1+, .NET 6/7/8/9
-
対応 C++: C++11 以上
-
最終更新: 2025-11-22
使用例:
ASP.NET Core MVC プロジェクトに国際化を追加:
言語: 英語(デフォルト)、日本語、中国語 リソース配置: Resources/ 機能:
- すべてのビュー、コントローラー
- データアノテーション
- クッキーベース言語切り替え
- 日付・通貨のロケール別フォーマット
完全な実装コード、設定ファイル、リソースファイルを生成してください。
または
C++ デスクトップアプリケーションにgettextを統合:
ライブラリ: gettext 言語: 英語、日本語、ドイツ語 機能:
- すべてのUI文字列
- エラーメッセージ
- 複数形対応
- CMake ビルド統合
セットアップコード、翻訳ファイル、ビルドスクリプトを生成してください。
エンタープライズレベルの国際化が実装されます!