Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/example_apple_metal/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ IMGUI_DIR = ../../
SOURCES = main.mm
SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp
SOURCES += $(IMGUI_DIR)/backends/imgui_impl_osx.mm $(IMGUI_DIR)/backends/imgui_impl_metal.mm
SOURCES += $(IMGUI_DIR)/misc/i18n/imgui_i18n.cpp
SOURCES += $(IMGUI_DIR)/misc/i18n/locale/zh_CN.cpp

CXXFLAGS = -std=c++11 -ObjC++ -fobjc-arc -Wall -Wextra -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends
CXXFLAGS = -std=c++11 -ObjC++ -fobjc-arc -Wall -Wextra -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I$(IMGUI_DIR)/misc/i18n -DIMGUI_DEMO_ENABLE_I18N
FRAMEWORKS = -framework AppKit -framework Metal -framework MetalKit -framework QuartzCore -framework GameController

all: $(EXE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/* Begin PBXBuildFile section */
05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05318E0E274C397200A8DE2E /* GameController.framework */; };
A1I18N00000000000000001B /* imgui_i18n.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1I18N00000000000000F001 /* imgui_i18n.cpp */; };
A1I18N00000000000000002B /* zh_CN.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1I18N00000000000000F002 /* zh_CN.cpp */; };
07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; };
07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; };
5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; };
Expand Down Expand Up @@ -59,6 +61,8 @@
83BBEA0220EB54E700295997 /* imgui_demo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_demo.cpp; path = ../../imgui_demo.cpp; sourceTree = "<group>"; };
83BBEA0320EB54E700295997 /* imgui.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui.cpp; path = ../../imgui.cpp; sourceTree = "<group>"; };
83BBEA0420EB54E700295997 /* imconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../../imconfig.h; sourceTree = "<group>"; };
A1I18N00000000000000F001 /* imgui_i18n.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_i18n.cpp; path = ../../misc/i18n/imgui_i18n.cpp; sourceTree = "<group>"; };
A1I18N00000000000000F002 /* zh_CN.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = zh_CN.cpp; path = ../../misc/i18n/locale/zh_CN.cpp; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -162,6 +166,8 @@
83BBEA0120EB54E700295997 /* imgui_draw.cpp */,
07A82ED62139413C0078D120 /* imgui_internal.h */,
07A82ED72139413C0078D120 /* imgui_widgets.cpp */,
A1I18N00000000000000F001 /* imgui_i18n.cpp */,
A1I18N00000000000000F002 /* zh_CN.cpp */,
);
name = imgui;
sourceTree = "<group>";
Expand Down Expand Up @@ -288,6 +294,8 @@
5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */,
07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */,
8309BDA8253CCC080045E2A1 /* main.mm in Sources */,
A1I18N00000000000000001B /* imgui_i18n.cpp in Sources (macOS) */,
A1I18N00000000000000002B /* zh_CN.cpp in Sources (macOS) */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -452,6 +460,10 @@
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
GCC_PREPROCESSOR_DEFINITIONS = (
"IMGUI_DEMO_ENABLE_I18N=1",
"$(inherited)",
);
INFOPLIST_FILE = "$(SRCROOT)/macOS/Info-macOS.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -473,6 +485,10 @@
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
GCC_PREPROCESSOR_DEFINITIONS = (
"IMGUI_DEMO_ENABLE_I18N=1",
"$(inherited)",
);
INFOPLIST_FILE = "$(SRCROOT)/macOS/Info-macOS.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
74 changes: 62 additions & 12 deletions examples/example_apple_metal/main.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
#import <MetalKit/MetalKit.h>

#include "imgui.h"
// i18n support: include unconditionally — when IMGUI_DEMO_ENABLE_I18N is not defined
// the header provides a no-op Tr(s) stub with zero overhead.
#include "misc/i18n/imgui_i18n.h"
#ifdef IMGUI_DEMO_ENABLE_I18N
#include <unistd.h>
#endif
#include "imgui_impl_metal.h"
#if TARGET_OS_OSX
#include "imgui_impl_osx.h"
Expand All @@ -38,6 +44,36 @@ @interface AppViewController () <MTKViewDelegate>
// AppViewController
//-----------------------------------------------------------------------------------

#ifdef IMGUI_DEMO_ENABLE_I18N
bool g_need_font_rebuild = false;

static void RebuildFonts()
{
ImGuiIO& io = ImGui::GetIO();
io.Fonts->ClearFonts();
io.Fonts->AddFontDefault();

// Merge a CJK font so the Language menu and translated strings render correctly.
// In v1.92+ with an updated backend, glyph ranges are handled dynamically and do not need to be specified.
// Hiragino Sans GB is designed for Simplified Chinese; fall back to STHeiti.
const char* font_paths[] = {
"/System/Library/Fonts/Hiragino Sans GB.ttc",
"/System/Library/Fonts/STHeiti Light.ttc",
nullptr
};
const char* chosen = nullptr;
for (int i = 0; font_paths[i]; ++i) {
if (access(font_paths[i], R_OK) == 0) { chosen = font_paths[i]; break; }
}
if (chosen) {
ImFontConfig cfg;
cfg.MergeMode = true;
io.Fonts->AddFontFromFileTTF(chosen, 0.0f, &cfg); // 0.0f = implicit ref size, matches AddFontDefault()
}
io.Fonts->Build();
}
#endif // IMGUI_DEMO_ENABLE_I18N

@implementation AppViewController

-(instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
Expand Down Expand Up @@ -110,6 +146,11 @@ -(void)viewDidLoad
ImGui_ImplOSX_Init(self.view);
[NSApp activateIgnoringOtherApps:YES];
#endif

#ifdef IMGUI_DEMO_ENABLE_I18N
// Build initial font atlas with CJK glyphs so the Language menu renders correctly from the start.
RebuildFonts();
#endif
}

-(void)drawInMTKView:(MTKView*)view
Expand All @@ -134,6 +175,14 @@ -(void)drawInMTKView:(MTKView*)view
return;
}

#ifdef IMGUI_DEMO_ENABLE_I18N
// Rebuild font atlas after language switch (must be before NewFrame to avoid use-after-free).
if (g_need_font_rebuild) {
g_need_font_rebuild = false;
RebuildFonts();
}
#endif

// Start the Dear ImGui frame
ImGui_ImplMetal_NewFrame(renderPassDescriptor);
#if TARGET_OS_OSX
Expand All @@ -155,30 +204,31 @@ -(void)drawInMTKView:(MTKView*)view
static float f = 0.0f;
static int counter = 0;

ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
// "Hello, world!" is the canonical ImGui getting-started window title — kept in English.
ImGui::Begin("Hello, world!");

ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::Text("This is some useful text."); // standard example text, kept in English
ImGui::Checkbox(Tr("Demo Window"), &show_demo_window);
ImGui::Checkbox(Tr("Another Window"), &show_another_window);

ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // "float" labels the C++ type, not translated
ImGui::ColorEdit3(Tr("clear color"), (float*)&clear_color);

if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
if (ImGui::Button(Tr("Button")))
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
ImGui::Text("counter = %d", counter); // variable name, not translated

ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::Text(Tr("Application average %.3f ms/frame (%.1f FPS)"), 1000.0f / io.Framerate, io.Framerate);
ImGui::End();
}

// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
ImGui::Begin(Tr("Another Window"), &show_another_window);
ImGui::Text("%s", Tr("Hello from another window!"));
if (ImGui::Button(Tr("Close Me")))
show_another_window = false;
ImGui::End();
}
Expand Down
206 changes: 206 additions & 0 deletions i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Dear ImGui — i18n (Internationalization)

Runtime language switching for `imgui_demo.cpp`.
Currently ships with **Simplified Chinese** (`zh_CN`).

---

## How It Works

| Component | Role |
|-----------|------|
| `imgui_i18n.h` | `Tr()` macro + namespace declaration |
| `imgui_i18n.cpp` | Lookup table, fallback, `$1`/`$2` placeholder substitution |
| `locale/zh_CN.cpp` | Chinese translation table (static initializer, auto-registered) |
| `tools/extract_strings.py` | Helper to extract translatable strings from a source range |

Every user-visible string literal in `imgui_demo.cpp` is wrapped with `Tr("...")`.
`Tr()` looks up the active locale table and returns the translated string, or falls back to the original English if no translation exists.

### Opt-in build flag

Define **`IMGUI_DEMO_ENABLE_I18N`** in your build to activate i18n.
Without it, `Tr(s)` expands to `(s)` at compile time — zero overhead, no extra files needed.

---

## Quick Start (macOS example_apple_metal)

```bash
# Makefile already includes IMGUI_DEMO_ENABLE_I18N and the i18n sources.
cd examples/example_apple_metal
make && ./example_apple_metal
# Open the Language menu in the demo window menu bar → switch to 中文
```

For other platforms/examples, see **Integrating into Your Build** below.

---

## Adding a New Language

### 1. Create a locale file

Copy `locale/zh_CN.cpp` to `locale/<locale_id>.cpp` (e.g. `locale/ja_JP.cpp`).

```cpp
// locale/ja_JP.cpp
#include "../imgui_i18n.h"

namespace {
struct JaJPRegistrar {
JaJPRegistrar() {
imgui_i18n::registerLocale("ja_JP", {
{"Configuration", "設定"},
{"Dear ImGui Demo","Dear ImGui デモ"},
// ... add all entries
});
}
} s_ja_jp;
} // namespace
```

**Key rules:**
- The **key** is the exact English string as it appears in `imgui_demo.cpp` (including punctuation and newlines).
- Missing keys automatically fall back to English — you can ship a partial translation.
- For strings containing `##` (ImGui internal IDs like `"Configuration##2"`), the key in the table is only the visible part (`"Configuration"`); the `##2` suffix is appended in code.

### 2. Add the locale file to your build

**Makefile (Linux/macOS):**
```makefile
SOURCES += $(IMGUI_DIR)/i18n/imgui_i18n.cpp
SOURCES += $(IMGUI_DIR)/i18n/locale/ja_JP.cpp # add your new locale
CXXFLAGS += -I$(IMGUI_DIR)/i18n -DIMGUI_DEMO_ENABLE_I18N
```

**CMake:**
```cmake
target_sources(my_app PRIVATE
${IMGUI_DIR}/i18n/imgui_i18n.cpp
${IMGUI_DIR}/i18n/locale/ja_JP.cpp
)
target_include_directories(my_app PRIVATE ${IMGUI_DIR}/i18n)
target_compile_definitions(my_app PRIVATE IMGUI_DEMO_ENABLE_I18N)
```

**Xcode:** Add both `.cpp` files to the target's *Compile Sources* phase.

### 3. Add a menu item for the new language

In `imgui_demo.cpp`, find the Language menu (search for `BeginMenu(Tr("Language"))`):

```cpp
if (ImGui::MenuItem("日本語", nullptr, is_ja) && !is_ja)
{
imgui_i18n::setLocale("ja_JP");
#if defined(__APPLE__)
g_need_font_rebuild = true; // triggers CJK font reload on Apple Metal
#endif
}
```

### 4. Font loading (CJK / non-Latin scripts)

For languages requiring non-Latin glyphs, add font loading logic in your platform's
`main` file (see `examples/example_apple_metal/main.mm` → `RebuildFonts()` for reference).

The pattern:
1. Call `io.Fonts->AddFontDefault(&cfg)` with explicit `SizePixels` first (required by ImGui v1.92+).
2. Merge the target-language font with `cfg.MergeMode = true`.
3. Choose glyph ranges: use `io.Fonts->GetGlyphRangesChineseSimplifiedCommon()` for Chinese,
`GetGlyphRangesJapanese()` for Japanese, etc.
4. Call `io.Fonts->Build()`.

---

## Translation Guidelines

Follow these rules when translating strings to ensure quality and consistency.

### What to translate

Translate user-visible UI labels: menu items, button labels, window titles, section
headers, tooltip descriptions, and status messages.

### What NOT to translate

| Category | Example | Reason |
|----------|---------|--------|
| Universally recognized programming examples | `"Hello, world!"` | Part of global programming culture; keeping English avoids confusion for learners following tutorials |
| Standard boilerplate example text | `"This is some useful text."` | Canonical ImGui getting-started template text |
| C++ type/variable names used as labels | `"float"`, `"counter = %d"` | Labels the underlying type or variable name, not a description |
| Intentional filler/nonsense | `"blah blah blah"` | The meaninglessness is the message; translating it to real words defeats the purpose |
| API / function names | `"IsItemHovered()"` | Must remain identical to the actual API; adding an entry that maps to itself is redundant |
| Brand / product names | `"Dear ImGui"`, `"dear imgui"` | Proper nouns; do not translate |
| `##`-suffixed ImGui IDs | `"Config##2"` | The `##` part is a disambiguation suffix handled in code, not user-visible |

### Translation quality rules

1. **Accuracy over literalness.** Translate meaning, not word-by-word.
Bad: `Metrics` → `性能` (performance)
Good: `Metrics` → `指标` (indicators/measurements)

2. **Preserve format specifiers.** `%d`, `%s`, `%.3f`, `$1`, `$2` must appear unchanged in the translated string.

3. **Preserve newlines.** `\n` positions in the translation may differ from the English, but must be present where the UI needs line breaks.

4. **Preserve punctuation intent.** If the English ends with `:`, the translation should too (adjusted for the target language's punctuation conventions).

5. **Redundant identity entries are noise.** If a string needs no translation (e.g. an API function name), do **not** add `{"Foo()", "Foo()"}` to the table. The fallback mechanism returns the key automatically.

---

## Extracting Strings for Translation

Use the helper script to generate a translation skeleton for a range of lines:

```bash
cd imgui-master # or wherever imgui_demo.cpp lives
python3 i18n/tools/extract_strings.py imgui_demo.cpp <start_line> <end_line>
```

Output is ready-to-paste `{"English key", ""},` entries.
Paste them into your locale file and fill in the translated values.

---

## Integrating into Your Build (Other Examples)

Only `example_apple_metal` ships with i18n enabled by default.
To enable it for any other example:

1. Add `imgui_i18n.cpp` and your locale `.cpp` files to the build.
2. Add `-I<imgui_root>/i18n` to include paths.
3. Add `-DIMGUI_DEMO_ENABLE_I18N` to compiler flags.
4. Add font loading + language-switch UI to your platform's `main` file
(use `examples/example_apple_metal/main.mm` as a reference).

---

## API Reference

```cpp
// imgui_i18n.h (available when IMGUI_DEMO_ENABLE_I18N is defined)

Tr("key") // translate key, return const char*
TrF("Draw $1 of $2", {"3","10"}) // translate + substitute placeholders

imgui_i18n::setLocale("zh_CN"); // switch language (call before NewFrame)
imgui_i18n::getLocale(); // returns current locale string ("" = English)
imgui_i18n::registerLocale( // called by locale/*.cpp static initializers
"locale_id",
{ {"English key", "Translation"}, ... }
);
```

---

## Current Translations

| Locale | Language | Coverage |
|--------|----------|----------|
| *(default)* | English | 100% (source) |
| `zh_CN` | Simplified Chinese | ~100% of `imgui_demo.cpp` |

Contributions welcome — see **Adding a New Language** above.
Loading