mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
gtk: add localization support, take 3 (#6004)
This is my third (!) attempt at implementing localization support. By leveraging GTK builder to do most of the `gettext` calls, I can avoid the whole mess about missing symbols on non-glibc platforms. Added some documentation too for contributors and translators, just for good measure. Supersedes #5214, resolves the GTK half of #2357
This commit is contained in:
37
.github/workflows/test.yml
vendored
37
.github/workflows/test.yml
vendored
@ -25,6 +25,7 @@ jobs:
|
|||||||
- prettier
|
- prettier
|
||||||
- alejandra
|
- alejandra
|
||||||
- typos
|
- typos
|
||||||
|
- translations
|
||||||
- test-pkg-linux
|
- test-pkg-linux
|
||||||
- test-debian-12
|
- test-debian-12
|
||||||
steps:
|
steps:
|
||||||
@ -593,6 +594,42 @@ jobs:
|
|||||||
- name: typos check
|
- name: typos check
|
||||||
run: nix develop -c typos
|
run: nix develop -c typos
|
||||||
|
|
||||||
|
translations:
|
||||||
|
if: github.repository == 'ghostty-org/ghostty'
|
||||||
|
runs-on: namespace-profile-ghostty-sm
|
||||||
|
timeout-minutes: 60
|
||||||
|
env:
|
||||||
|
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||||
|
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||||
|
- name: Setup Cache
|
||||||
|
uses: namespacelabs/nscloud-cache-action@v1.2.0
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
/nix
|
||||||
|
/zig
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: ghostty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
skipPush: true
|
||||||
|
useDaemon: false # sometimes fails on short jobs
|
||||||
|
- name: check translations
|
||||||
|
run: |
|
||||||
|
old_pot=$(mktemp)
|
||||||
|
cp po/com.mitchellh.ghostty.pot "$old_pot"
|
||||||
|
nix develop -c zig build update-translations
|
||||||
|
|
||||||
|
# Compare previous POT to current POT
|
||||||
|
msgcmp "$old_pot" po/com.mitchellh.ghostty.pot --use-untranslated
|
||||||
|
|
||||||
|
# Compare all other POs to current POT
|
||||||
|
for f in po/*.po; do msgcmp "$f" po/com.mitchellh.ghostty.pot; done
|
||||||
|
|
||||||
test-pkg-linux:
|
test-pkg-linux:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
@ -21,6 +21,15 @@ All issues are actionable. Pick one and start working on it. Thank you.
|
|||||||
If you need help or guidance, comment on the issue. Issues that are extra
|
If you need help or guidance, comment on the issue. Issues that are extra
|
||||||
friendly to new contributors are tagged with "contributor friendly".
|
friendly to new contributors are tagged with "contributor friendly".
|
||||||
|
|
||||||
|
**I'd like to translate Ghostty to my language!**
|
||||||
|
|
||||||
|
We have written a [Translator's Guide](po/README_CONTRIBUTORS.md) for
|
||||||
|
everyone interested in contributing translations to Ghostty.
|
||||||
|
Translations usually do not need to go through the process of issue triage
|
||||||
|
and you can submit pull requests directly, although please make sure that
|
||||||
|
our [Style Guide](po/README_CONTRIBUTORS.md#style-guide) is followed before
|
||||||
|
submission.
|
||||||
|
|
||||||
**I have a bug!**
|
**I have a bug!**
|
||||||
|
|
||||||
1. Search the issue tracker and discussions for similar issues.
|
1. Search the issue tracker and discussions for similar issues.
|
||||||
|
@ -11,6 +11,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
// Ghostty resources like terminfo, shell integration, themes, etc.
|
// Ghostty resources like terminfo, shell integration, themes, etc.
|
||||||
const resources = try buildpkg.GhosttyResources.init(b, &config);
|
const resources = try buildpkg.GhosttyResources.init(b, &config);
|
||||||
|
const i18n = try buildpkg.GhosttyI18n.init(b, &config);
|
||||||
|
|
||||||
// Ghostty dependencies used by many artifacts.
|
// Ghostty dependencies used by many artifacts.
|
||||||
const deps = try buildpkg.SharedDeps.init(b, &config);
|
const deps = try buildpkg.SharedDeps.init(b, &config);
|
||||||
@ -39,6 +40,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
if (config.app_runtime != .none) {
|
if (config.app_runtime != .none) {
|
||||||
exe.install();
|
exe.install();
|
||||||
resources.install();
|
resources.install();
|
||||||
|
i18n.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Libghostty
|
// Libghostty
|
||||||
@ -103,4 +105,11 @@ pub fn build(b: *std.Build) !void {
|
|||||||
test_step.dependOn(&test_run.step);
|
test_step.dependOn(&test_run.step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update-translations does what it sounds like and updates the "pot"
|
||||||
|
// files. These should be committed to the repo.
|
||||||
|
{
|
||||||
|
const step = b.step("update-translations", "Update translation files");
|
||||||
|
step.dependOn(i18n.update_step);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
gobject-introspection,
|
gobject-introspection,
|
||||||
libadwaita,
|
libadwaita,
|
||||||
blueprint-compiler,
|
blueprint-compiler,
|
||||||
|
gettext,
|
||||||
adwaita-icon-theme,
|
adwaita-icon-theme,
|
||||||
hicolor-icon-theme,
|
hicolor-icon-theme,
|
||||||
harfbuzz,
|
harfbuzz,
|
||||||
@ -129,6 +130,9 @@ in
|
|||||||
# wasm
|
# wasm
|
||||||
wabt
|
wabt
|
||||||
wasmtime
|
wasmtime
|
||||||
|
|
||||||
|
# Localization
|
||||||
|
gettext
|
||||||
]
|
]
|
||||||
++ lib.optionals stdenv.hostPlatform.isLinux [
|
++ lib.optionals stdenv.hostPlatform.isLinux [
|
||||||
# My nix shell environment installs the non-interactive version
|
# My nix shell environment installs the non-interactive version
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
libadwaita,
|
libadwaita,
|
||||||
blueprint-compiler,
|
blueprint-compiler,
|
||||||
libxml2,
|
libxml2,
|
||||||
|
gettext,
|
||||||
wrapGAppsHook4,
|
wrapGAppsHook4,
|
||||||
gsettings-desktop-schemas,
|
gsettings-desktop-schemas,
|
||||||
git,
|
git,
|
||||||
@ -64,6 +65,7 @@ in
|
|||||||
../dist/linux
|
../dist/linux
|
||||||
../images
|
../images
|
||||||
../include
|
../include
|
||||||
|
../po
|
||||||
../pkg
|
../pkg
|
||||||
../src
|
../src
|
||||||
../vendor
|
../vendor
|
||||||
@ -87,6 +89,7 @@ in
|
|||||||
wrapGAppsHook4
|
wrapGAppsHook4
|
||||||
blueprint-compiler
|
blueprint-compiler
|
||||||
libxml2 # for xmllint
|
libxml2 # for xmllint
|
||||||
|
gettext
|
||||||
]
|
]
|
||||||
++ lib.optionals enableWayland [
|
++ lib.optionals enableWayland [
|
||||||
wayland-scanner
|
wayland-scanner
|
||||||
|
72
po/README_CONTRIBUTORS.md
Normal file
72
po/README_CONTRIBUTORS.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# Localizing Ghostty: The Contributors' Guide
|
||||||
|
|
||||||
|
Ghostty uses the `gettext` library/framework for localization, which has the
|
||||||
|
distinct benefit of being able to be consumed directly by our two main
|
||||||
|
app runtimes: macOS and GTK (Linux). The core would ideally remain agnostic
|
||||||
|
to localization efforts, as not all consumers of libghostty would be interested
|
||||||
|
in localization support. Thus, implementors of app runtimes are left responsible
|
||||||
|
for any localization that they may add.
|
||||||
|
|
||||||
|
## GTK
|
||||||
|
|
||||||
|
In the GTK app runtime, translable strings are mainly sourced from Blueprint
|
||||||
|
files (located under `src/apprt/gtk/ui`). Blueprints have a native syntax for
|
||||||
|
translatable strings, which look like this:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
// Translators: This is the name of the button that opens the about dialog.
|
||||||
|
title: _("About Ghostty");
|
||||||
|
```
|
||||||
|
|
||||||
|
The `// Translators:` comment provides additional context to the translator
|
||||||
|
if the string itself is unclear as to what its purpose is or where it's located.
|
||||||
|
|
||||||
|
By default identical strings are collapsed together into one translatable entry.
|
||||||
|
To avoid this, assign a _context_ to the string:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
label: C_("menu action", "Copy");
|
||||||
|
```
|
||||||
|
|
||||||
|
Translatable strings can also be sourced from Zig source files. This is useful
|
||||||
|
when the string must be chosen dynamically at runtime, or when it requires
|
||||||
|
additional formatting. The `i18n.` prefix is necessary as `_` is not allowed
|
||||||
|
as a bare identifier in Zig.
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const i18n = @import("i18n.zig");
|
||||||
|
|
||||||
|
const text = if (awesome)
|
||||||
|
i18n._("My awesome label :D")
|
||||||
|
else
|
||||||
|
i18n._("My not-so-awesome label :(");
|
||||||
|
|
||||||
|
const label = gtk.Label.new(text);
|
||||||
|
```
|
||||||
|
|
||||||
|
All translatable strings are extracted into the _translation template file_,
|
||||||
|
located under `po/com.mitchellh.ghostty.pot`. **This file must stay in sync with
|
||||||
|
the list of translatable strings present in source code or Blueprints at all times.**
|
||||||
|
A CI action would be run for every PR, which checks if the translation template
|
||||||
|
requires any updates. You can update the translation template by running
|
||||||
|
`zig build update-translations`, which would also synchronize translation files
|
||||||
|
for other locales (`.po` files) to reflect the state of the template file.
|
||||||
|
|
||||||
|
During the build process, each locale in `.po` files is compiled
|
||||||
|
into binary `.mo` files, stored under `share/locale/<LOCALE>/LC_MESSAGES/com.mitchellh.ghostty.mo`.
|
||||||
|
This can be directly accessed by `libintl`, which provide the various `gettext`
|
||||||
|
C functions that can be called either by Zig code directly, or by the GTK builder
|
||||||
|
(recommended).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> For the vast majority of users, no additional library needs to be installed
|
||||||
|
> in order to get localizations, since `libintl` is a part of the GNU C standard
|
||||||
|
> library. For users using alternative C standard libraries like musl, they must
|
||||||
|
> use a stub implementation such as [`gettext-tiny`](https://github.com/sabotage-linux/gettext-tiny)
|
||||||
|
> that offer no-op symbols for the translation functions, or by using a build of
|
||||||
|
> `libintl` that works for them.
|
||||||
|
|
||||||
|
## macOS
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The localization system is not yet implemented for macOS.
|
183
po/README_TRANSLATORS.md
Normal file
183
po/README_TRANSLATORS.md
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
# Localizing Ghostty: The Translators' Guide
|
||||||
|
|
||||||
|
First of all, thanks for helping us localize Ghostty!
|
||||||
|
|
||||||
|
To begin contributing, please make sure that you have installed `gettext`
|
||||||
|
on your system, which should be available under the `gettext` package
|
||||||
|
for most Linux and macOS package managers.
|
||||||
|
|
||||||
|
You can install `gettext` on Windows in a few ways:
|
||||||
|
|
||||||
|
- Through [an installer](https://mlocati.github.io/articles/gettext-iconv-windows.html);
|
||||||
|
- Through package managers like Scoop (`scoop install gettext`) and
|
||||||
|
WinGet (`winget install gettext`) which repackages the aforementioned installer;
|
||||||
|
- Through Unix-like environments like [Cygwin](https://cygwin.com/cygwin/packages/summary/gettext.html)
|
||||||
|
and [MSYS2](https://packages.msys2.org/base/gettext).
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> Unlike what some tutorials suggest, **we do not recommend installing `gettext`
|
||||||
|
> through GnuWin32**, since it hasn't been updated since 2010 and very likely
|
||||||
|
> does not work on modern Windows versions.
|
||||||
|
|
||||||
|
You can verify that `gettext` has been successfully installed by running the
|
||||||
|
command `gettext -V`. If everything went correctly, you should see an output like this:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ gettext -V
|
||||||
|
gettext (GNU gettext-runtime) 0.21.1
|
||||||
|
Copyright (C) 1995-2022 Free Software Foundation, Inc.
|
||||||
|
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
|
||||||
|
This is free software: you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
|
Written by Ulrich Drepper.
|
||||||
|
```
|
||||||
|
|
||||||
|
With this, you're ready to localize!
|
||||||
|
|
||||||
|
## Editing translation files
|
||||||
|
|
||||||
|
All translation files lie in the `po/` directory, including the main _template_
|
||||||
|
file called `com.mitchellh.ghostty.pot`. **Do not edit this file.** The
|
||||||
|
template is generated automatically from Ghostty's code and resources, and are
|
||||||
|
intended to be regenerated by code contributors. If there is a problem with
|
||||||
|
the template file, please reach out to a code contributor.
|
||||||
|
|
||||||
|
Instead, only edit the translation file corresponding to your language/locale,
|
||||||
|
identified via the its _locale name_: for example, `de_DE.UTF-8.po` would be
|
||||||
|
the translation file for German (language code `de`) as spoken in Germany
|
||||||
|
(country code `DE`). The GNU `gettext` manual contains
|
||||||
|
[further information about locale names](https://www.gnu.org/software/gettext/manual/gettext.html#Locale-Names-1),
|
||||||
|
including a list of language and country codes.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> If the translation file for your locale does not yet exist, see the
|
||||||
|
> ["Creating new translation files" section](#creating-new-translation-files)
|
||||||
|
> of this document on how to create one.
|
||||||
|
|
||||||
|
The `.po` file contains a list of entries that look like this:
|
||||||
|
|
||||||
|
```po
|
||||||
|
#. Translators: the category in the right-click context menu that contains split items for all directions
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context-menu.blp:38
|
||||||
|
# 译注:其他终端程序对 Split 皆有不同翻译,此处采取最直观的翻译方式
|
||||||
|
msgctxt "Context menu"
|
||||||
|
msgid "Split"
|
||||||
|
msgstr "分屏"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `msgid` line contains the original string in English, and the `msgstr` line
|
||||||
|
should contain the translation for your language. Occasionally there will also
|
||||||
|
be a `msgctxt` line that differentiates strings that are identical in English,
|
||||||
|
based on its context.
|
||||||
|
|
||||||
|
Lines beginning with `#` are comments, of which there are several kinds:
|
||||||
|
|
||||||
|
- Pay attention to comments beginning with `#.`. They are comments left
|
||||||
|
by _developers_ providing more context to the string.
|
||||||
|
|
||||||
|
- Comments that begin with `#:` are _source comments_: they link
|
||||||
|
the string to source code or resource files. You normally don't need to
|
||||||
|
consider these in your translations.
|
||||||
|
|
||||||
|
- You may also leave comments of your own to be read by _other translators_,
|
||||||
|
beginning with `# `. These comments are specific to your locale and don't
|
||||||
|
affect translators in other locales.
|
||||||
|
|
||||||
|
The first entry of the `.po` file has an empty `msgid`. This entry is special
|
||||||
|
as it stores the metadata related to the `.po` file itself. You usually do
|
||||||
|
not need to modify it.
|
||||||
|
|
||||||
|
## Creating new translation files
|
||||||
|
|
||||||
|
You can use the `msginit` tool to create new translation files.
|
||||||
|
|
||||||
|
Run the command below, optionally replacing `$LANG` with the name of a locale
|
||||||
|
that is _different_ to your system locale, or if the `LANG` environmental
|
||||||
|
variable is not set.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ msginit -i po/com.mitchellh.ghostty.pot -l $LANG -o "po/$LANG.po"
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> Ghostty enforces the convention that all parts of the locale, including the
|
||||||
|
> language code, country code, encoding, and possible regional variants
|
||||||
|
> **must** be communicated in the file name. Files like `pt.po` are not
|
||||||
|
> acceptable, while `pt_BR.UTF-8.po` is.
|
||||||
|
>
|
||||||
|
> This is to allow us to more easily accommodate regional variants of a
|
||||||
|
> language in the future, and to reject translations that may not be applicable
|
||||||
|
> to all speakers of a language (e.g. an unqualified `zh.po` may contain
|
||||||
|
> terminology specific to Chinese speakers in Mainland China, which are not
|
||||||
|
> found in Taiwan. Using `zh_CN.UTF-8.po` would allow that difference to be
|
||||||
|
> communicated.)
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> **Make sure your selected locale uses the UTF-8 encoding, as it is the sole
|
||||||
|
> encoding supported by Ghostty and its dependencies.**
|
||||||
|
>
|
||||||
|
> For backwards compatibility reasons, some locales may default to a non-UTF-8
|
||||||
|
> encoding when an encoding is not specified. For instance, `de_DE` defaults
|
||||||
|
> to using the legacy ISO-8859-1 encoding, which is incompatible with UTF-8.
|
||||||
|
> You need to manually instruct `msginit` to use UTF-8 in these instances,
|
||||||
|
> by appending `.UTF-8` to the end of the locale name (e.g. `de_DE.UTF-8`).
|
||||||
|
|
||||||
|
`msginit` may prompt you for other information such as your email address,
|
||||||
|
which should be filled in accordingly. You can then add your translations
|
||||||
|
within the newly created translation file.
|
||||||
|
|
||||||
|
Afterwards, you need to update the list of known locales within Ghostty's
|
||||||
|
build system. To do so, open `src/build/GhosttyI18n.zig` and find the list
|
||||||
|
of locales under the `locale` variable, then append the full locale name
|
||||||
|
into the list.
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const locales = [_][]const u8{
|
||||||
|
"zh_CN.UTF-8",
|
||||||
|
// <- Add your locale here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You should then be able to run `zig build` and see your translations in action.
|
||||||
|
|
||||||
|
## Style Guide
|
||||||
|
|
||||||
|
These are general style guidelines for translations. Naturally, the specific
|
||||||
|
recommended standards will differ based on the specific language/locale,
|
||||||
|
but these should serve as a baseline for the tone and voice of any translation.
|
||||||
|
|
||||||
|
- **Prefer an instructive, yet professional tone.**
|
||||||
|
|
||||||
|
In languages that exhibit distinctions based on formality,
|
||||||
|
prefer the formality that is expected of instructive material on the internet.
|
||||||
|
The user should be considered an equal peer of the program and the translator,
|
||||||
|
not an esteemed customer.
|
||||||
|
|
||||||
|
- **Use simple to understand language and avoid jargon.**
|
||||||
|
|
||||||
|
Explain concepts that may be familiar in an English-speaking context,
|
||||||
|
but are uncommon in your language.
|
||||||
|
|
||||||
|
- **Do not overly literally translate foreign concepts to your language.**
|
||||||
|
|
||||||
|
Care should be taken so that your translations make sense to a reader without
|
||||||
|
any background knowledge of the English source text. To _localize_ is to
|
||||||
|
transfer a concept between languages, not to translate each word at face value.
|
||||||
|
|
||||||
|
- **Be consistent with stylistic and tonal choices.**
|
||||||
|
|
||||||
|
Consult the translations made by previous translators, and try to emulate them.
|
||||||
|
Do not overwrite someone else's hard work without substantial justification.
|
||||||
|
|
||||||
|
- **Make Ghostty fit in with surrounding applications.**
|
||||||
|
|
||||||
|
Follow existing translations for terms and concepts if possible, even when
|
||||||
|
they are suboptimal. Follow the writing styles prescribed by the human
|
||||||
|
interface guidelines of each platform Ghostty is available for, including the
|
||||||
|
[GNOME Human Interface Guidelines](https://developer.gnome.org/hig/guidelines/writing-style.html)
|
||||||
|
on Linux, and [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/writing)
|
||||||
|
on macOS.
|
204
po/com.mitchellh.ghostty.pot
Normal file
204
po/com.mitchellh.ghostty.pot
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR Mitchell Hashimoto
|
||||||
|
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||||
|
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||||
|
"POT-Creation-Date: 2025-02-28 22:12+0100\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||||
|
msgid "Change Terminal Title"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||||
|
msgid "Leave blank to restore the default title."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||||
|
msgid "OK"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||||
|
msgid "Copy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11
|
||||||
|
msgid "Paste"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
|
||||||
|
msgid "Clear"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
|
||||||
|
msgid "Split"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
|
||||||
|
msgid "Change Title…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
|
||||||
|
msgid "Split Up"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
|
||||||
|
msgid "Split Down"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
|
||||||
|
msgid "Split Left"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
|
||||||
|
msgid "Split Right"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
|
||||||
|
msgid "Tab"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
|
||||||
|
#: src/apprt/gtk/Window.zig:239
|
||||||
|
msgid "New Tab"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35
|
||||||
|
msgid "Close Tab"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
|
||||||
|
msgid "Window"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18
|
||||||
|
msgid "New Window"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23
|
||||||
|
msgid "Close Window"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||||
|
msgid "Config"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||||
|
msgid "Open Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||||
|
msgid "Reload Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||||
|
msgid "Terminal Inspector"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102
|
||||||
|
#: src/apprt/gtk/Window.zig:923
|
||||||
|
msgid "About Ghostty"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6
|
||||||
|
msgid "Authorize Clipboard Access"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||||
|
msgid ""
|
||||||
|
"An application is attempting to read from the clipboard. The current "
|
||||||
|
"clipboard contents are shown below."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
|
||||||
|
msgid "Deny"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||||
|
msgid "Allow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||||
|
msgid ""
|
||||||
|
"An application is attempting to write to the clipboard. The current "
|
||||||
|
"clipboard contents are shown below."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6
|
||||||
|
msgid "Warning: Potentially Unsafe Paste"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7
|
||||||
|
msgid ""
|
||||||
|
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||||
|
"commands may be executed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:192
|
||||||
|
msgid "Main Menu"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:212
|
||||||
|
msgid "View Open Tabs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:257
|
||||||
|
msgid ""
|
||||||
|
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:660
|
||||||
|
msgid "Reloaded the configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:904
|
||||||
|
msgid "Ghostty Developers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Surface.zig:1236
|
||||||
|
msgid "Copied to clipboard"
|
||||||
|
msgstr ""
|
204
po/zh_CN.UTF-8.po
Normal file
204
po/zh_CN.UTF-8.po
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Chinese translations for com.mitchellh.ghostty package
|
||||||
|
# com.mitchellh.ghostty 软件包的简体中文翻译.
|
||||||
|
# Copyright (C) 2025 Mitchell Hashimoto
|
||||||
|
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||||
|
# Leah <hi@pluie.me>, 2025.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||||
|
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||||
|
"POT-Creation-Date: 2025-02-28 22:12+0100\n"
|
||||||
|
"PO-Revision-Date: 2025-02-27 09:16+0100\n"
|
||||||
|
"Last-Translator: Leah <hi@pluie.me>\n"
|
||||||
|
"Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n"
|
||||||
|
"Language: zh_CN\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||||
|
msgid "Change Terminal Title"
|
||||||
|
msgstr "更改终端标题"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||||
|
msgid "Leave blank to restore the default title."
|
||||||
|
msgstr "留空以重置至默认标题。"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "取消"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||||
|
msgid "OK"
|
||||||
|
msgstr "确认"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||||
|
msgid "Copy"
|
||||||
|
msgstr "复制"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11
|
||||||
|
msgid "Paste"
|
||||||
|
msgstr "粘贴"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
|
||||||
|
msgid "Clear"
|
||||||
|
msgstr "清除界面"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "重置终端"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
|
||||||
|
msgid "Split"
|
||||||
|
msgstr "分屏"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
|
||||||
|
msgid "Change Title…"
|
||||||
|
msgstr "更改标题……"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
|
||||||
|
msgid "Split Up"
|
||||||
|
msgstr "向上分屏"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
|
||||||
|
msgid "Split Down"
|
||||||
|
msgstr "向下分屏"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
|
||||||
|
msgid "Split Left"
|
||||||
|
msgstr "向左分屏"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
|
||||||
|
msgid "Split Right"
|
||||||
|
msgstr "向右分屏"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
|
||||||
|
msgid "Tab"
|
||||||
|
msgstr "标签页"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
|
||||||
|
#: src/apprt/gtk/Window.zig:239
|
||||||
|
msgid "New Tab"
|
||||||
|
msgstr "新建标签页"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35
|
||||||
|
msgid "Close Tab"
|
||||||
|
msgstr "关闭标签页"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
|
||||||
|
msgid "Window"
|
||||||
|
msgstr "窗口"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18
|
||||||
|
msgid "New Window"
|
||||||
|
msgstr "新建窗口"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23
|
||||||
|
msgid "Close Window"
|
||||||
|
msgstr "关闭窗口"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||||
|
msgid "Config"
|
||||||
|
msgstr "设置"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||||
|
msgid "Open Configuration"
|
||||||
|
msgstr "打开设置文件"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||||
|
msgid "Reload Configuration"
|
||||||
|
msgstr "重新加载设置"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||||
|
msgid "Terminal Inspector"
|
||||||
|
msgstr "终端检视器"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102
|
||||||
|
#: src/apprt/gtk/Window.zig:923
|
||||||
|
msgid "About Ghostty"
|
||||||
|
msgstr "关于 Ghostty"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "退出"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6
|
||||||
|
msgid "Authorize Clipboard Access"
|
||||||
|
msgstr "剪切板访问授权"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||||
|
msgid ""
|
||||||
|
"An application is attempting to read from the clipboard. The current "
|
||||||
|
"clipboard contents are shown below."
|
||||||
|
msgstr "一个应用正在试图从剪切板读取内容。剪切板目前的内容如下:"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
|
||||||
|
msgid "Deny"
|
||||||
|
msgstr "拒绝"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||||
|
msgid "Allow"
|
||||||
|
msgstr "允许"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||||
|
msgid ""
|
||||||
|
"An application is attempting to write to the clipboard. The current "
|
||||||
|
"clipboard contents are shown below."
|
||||||
|
msgstr "一个应用正在试图向剪切板写入内容。剪切板目前的内容如下:"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6
|
||||||
|
msgid "Warning: Potentially Unsafe Paste"
|
||||||
|
msgstr "警告:粘贴内容可能不安全"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7
|
||||||
|
msgid ""
|
||||||
|
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||||
|
"commands may be executed."
|
||||||
|
msgstr "将以下内容粘贴至终端内将可能执行有害命令。"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:192
|
||||||
|
msgid "Main Menu"
|
||||||
|
msgstr "主菜单"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:212
|
||||||
|
msgid "View Open Tabs"
|
||||||
|
msgstr "浏览标签页"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:257
|
||||||
|
msgid ""
|
||||||
|
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||||
|
msgstr "⚠️ Ghostty 正在以调试模式运行!性能将大打折扣。"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:660
|
||||||
|
msgid "Reloaded the configuration"
|
||||||
|
msgstr "已重新加载设置"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Window.zig:904
|
||||||
|
msgid "Ghostty Developers"
|
||||||
|
msgstr "Ghostty 开发团队"
|
||||||
|
|
||||||
|
#: src/apprt/gtk/Surface.zig:1236
|
||||||
|
msgid "Copied to clipboard"
|
||||||
|
msgstr "已复制至剪切板"
|
@ -78,6 +78,7 @@ parts:
|
|||||||
- libxml2-utils
|
- libxml2-utils
|
||||||
- git
|
- git
|
||||||
- patchelf
|
- patchelf
|
||||||
|
- gettext
|
||||||
override-build: |
|
override-build: |
|
||||||
craftctl set version=$(git describe --abbrev=8)
|
craftctl set version=$(git describe --abbrev=8)
|
||||||
$CRAFT_PART_SRC/../../zig/src/zig build -Dpatch-rpath=\$ORIGIN/../usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/core24/current/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR -Doptimize=ReleaseFast
|
$CRAFT_PART_SRC/../../zig/src/zig build -Dpatch-rpath=\$ORIGIN/../usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/core24/current/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR -Doptimize=ReleaseFast
|
||||||
|
@ -39,6 +39,7 @@ const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
|
|||||||
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
||||||
const Split = @import("Split.zig");
|
const Split = @import("Split.zig");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
|
const i18n = @import("i18n.zig");
|
||||||
const version = @import("version.zig");
|
const version = @import("version.zig");
|
||||||
const inspector = @import("inspector.zig");
|
const inspector = @import("inspector.zig");
|
||||||
const key = @import("key.zig");
|
const key = @import("key.zig");
|
||||||
@ -98,6 +99,11 @@ quit_timer: union(enum) {
|
|||||||
pub fn init(core_app: *CoreApp, opts: Options) !App {
|
pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
_ = opts;
|
_ = opts;
|
||||||
|
|
||||||
|
// This can technically be placed *anywhere* because we don't have any
|
||||||
|
// localized log messages. It just has to be placed before any localized
|
||||||
|
// widgets are drawn.
|
||||||
|
try i18n.init(core_app.alloc);
|
||||||
|
|
||||||
// Log our GTK version
|
// Log our GTK version
|
||||||
log.info("GTK version build={d}.{d}.{d} runtime={d}.{d}.{d}", .{
|
log.info("GTK version build={d}.{d}.{d} runtime={d}.{d}.{d}", .{
|
||||||
c.GTK_MAJOR_VERSION,
|
c.GTK_MAJOR_VERSION,
|
||||||
|
@ -35,6 +35,7 @@ const gtk_key = @import("key.zig");
|
|||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
const Builder = @import("Builder.zig");
|
const Builder = @import("Builder.zig");
|
||||||
const adwaita = @import("adwaita.zig");
|
const adwaita = @import("adwaita.zig");
|
||||||
|
const i18n = @import("i18n.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk_surface);
|
const log = std.log.scoped(.gtk_surface);
|
||||||
|
|
||||||
@ -1152,7 +1153,7 @@ pub fn setClipboardString(
|
|||||||
self.app.config.@"app-notifications".@"clipboard-copy")
|
self.app.config.@"app-notifications".@"clipboard-copy")
|
||||||
{
|
{
|
||||||
if (self.container.window()) |window|
|
if (self.container.window()) |window|
|
||||||
window.sendToast("Copied to clipboard");
|
window.sendToast(i18n._("Copied to clipboard"));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ const TabView = @import("TabView.zig");
|
|||||||
const HeaderBar = @import("headerbar.zig");
|
const HeaderBar = @import("headerbar.zig");
|
||||||
const version = @import("version.zig");
|
const version = @import("version.zig");
|
||||||
const winproto = @import("winproto.zig");
|
const winproto = @import("winproto.zig");
|
||||||
|
const i18n = @import("i18n.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
@ -192,7 +193,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const btn = c.gtk_menu_button_new();
|
const btn = c.gtk_menu_button_new();
|
||||||
c.gtk_widget_set_tooltip_text(btn, "Main Menu");
|
c.gtk_widget_set_tooltip_text(btn, i18n._("Main Menu"));
|
||||||
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic");
|
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic");
|
||||||
c.gtk_menu_button_set_popover(@ptrCast(btn), @ptrCast(@alignCast(self.titlebar_menu.asWidget())));
|
c.gtk_menu_button_set_popover(@ptrCast(btn), @ptrCast(@alignCast(self.titlebar_menu.asWidget())));
|
||||||
_ = c.g_signal_connect_data(
|
_ = c.g_signal_connect_data(
|
||||||
@ -212,7 +213,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
const btn = switch (self.config.gtk_tabs_location) {
|
const btn = switch (self.config.gtk_tabs_location) {
|
||||||
.top, .bottom => btn: {
|
.top, .bottom => btn: {
|
||||||
const btn = c.gtk_toggle_button_new();
|
const btn = c.gtk_toggle_button_new();
|
||||||
c.gtk_widget_set_tooltip_text(btn, "View Open Tabs");
|
c.gtk_widget_set_tooltip_text(btn, i18n._("View Open Tabs"));
|
||||||
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
||||||
_ = c.g_object_bind_property(
|
_ = c.g_object_bind_property(
|
||||||
btn,
|
btn,
|
||||||
@ -239,7 +240,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic");
|
const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic");
|
||||||
c.gtk_widget_set_tooltip_text(btn, "New Tab");
|
c.gtk_widget_set_tooltip_text(btn, i18n._("New Tab"));
|
||||||
_ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT);
|
||||||
self.headerbar.packStart(btn);
|
self.headerbar.packStart(btn);
|
||||||
}
|
}
|
||||||
@ -257,7 +258,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
// This is a really common issue where people build from source in debug and performance is really bad.
|
// This is a really common issue where people build from source in debug and performance is really bad.
|
||||||
if (comptime std.debug.runtime_safety) {
|
if (comptime std.debug.runtime_safety) {
|
||||||
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||||
const warning_text = "⚠️ You're running a debug build of Ghostty! Performance will be degraded.";
|
const warning_text = i18n._("⚠️ You're running a debug build of Ghostty! Performance will be degraded.");
|
||||||
if (adwaita.versionAtLeast(1, 3, 0)) {
|
if (adwaita.versionAtLeast(1, 3, 0)) {
|
||||||
const banner = c.adw_banner_new(warning_text);
|
const banner = c.adw_banner_new(warning_text);
|
||||||
c.adw_banner_set_revealed(@ptrCast(banner), 1);
|
c.adw_banner_set_revealed(@ptrCast(banner), 1);
|
||||||
@ -674,10 +675,10 @@ pub fn focusCurrentTab(self: *Window) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn onConfigReloaded(self: *Window) void {
|
pub fn onConfigReloaded(self: *Window) void {
|
||||||
self.sendToast("Reloaded the configuration");
|
self.sendToast(i18n._("Reloaded the configuration"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendToast(self: *Window, title: [:0]const u8) void {
|
pub fn sendToast(self: *Window, title: [*:0]const u8) void {
|
||||||
const toast = c.adw_toast_new(title);
|
const toast = c.adw_toast_new(title);
|
||||||
c.adw_toast_set_timeout(toast, 3);
|
c.adw_toast_set_timeout(toast, 3);
|
||||||
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast);
|
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast);
|
||||||
@ -930,7 +931,7 @@ fn gtkActionAbout(
|
|||||||
"application-name",
|
"application-name",
|
||||||
name,
|
name,
|
||||||
"developer-name",
|
"developer-name",
|
||||||
"Ghostty Developers",
|
i18n._("Ghostty Developers"),
|
||||||
"application-icon",
|
"application-icon",
|
||||||
icon,
|
icon,
|
||||||
"version",
|
"version",
|
||||||
@ -949,7 +950,7 @@ fn gtkActionAbout(
|
|||||||
"logo-icon-name",
|
"logo-icon-name",
|
||||||
icon,
|
icon,
|
||||||
"title",
|
"title",
|
||||||
"About Ghostty",
|
i18n._("About Ghostty"),
|
||||||
"version",
|
"version",
|
||||||
build_config.version_string.ptr,
|
build_config.version_string.ptr,
|
||||||
"website",
|
"website",
|
||||||
|
37
src/apprt/gtk/i18n.zig
Normal file
37
src/apprt/gtk/i18n.zig
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//! I18n support for the GTK frontend based on gettext/libintl
|
||||||
|
//!
|
||||||
|
//! This is normally built into the C standard library for the *vast* majority
|
||||||
|
//! of users who use glibc, but for musl users we fall back to the `gettext-tiny`
|
||||||
|
//! stub implementation which provides all of the necessary interfaces.
|
||||||
|
//! Musl users who do want to use localization should know what they need to do.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const global = &@import("../../global.zig").state;
|
||||||
|
const build_config = @import("../../build_config.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.gtk_i18n);
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !void {
|
||||||
|
const resources_dir = global.resources_dir orelse {
|
||||||
|
log.warn("resource dir not found; not localizing", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const share_dir = std.fs.path.dirname(resources_dir) orelse {
|
||||||
|
log.warn("resource dir not placed in a share/ directory; not localizing", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const locale_dir = try std.fs.path.joinZ(alloc, &.{ share_dir, "locale" });
|
||||||
|
defer alloc.free(locale_dir);
|
||||||
|
|
||||||
|
// The only way these calls can fail is if we're out of memory
|
||||||
|
_ = bindtextdomain(build_config.bundle_id, locale_dir.ptr) orelse return error.OutOfMemory;
|
||||||
|
_ = textdomain(build_config.bundle_id) orelse return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually include function definitions for the gettext functions
|
||||||
|
// as libintl.h isn't always easily available (e.g. in musl)
|
||||||
|
extern fn bindtextdomain(domainname: [*:0]const u8, dirname: [*:0]const u8) ?[*:0]const u8;
|
||||||
|
extern fn textdomain(domainname: [*:0]const u8) ?[*:0]const u8;
|
||||||
|
pub extern fn gettext(msgid: [*:0]const u8) [*:0]const u8;
|
||||||
|
pub const _ = gettext;
|
118
src/build/GhosttyI18n.zig
Normal file
118
src/build/GhosttyI18n.zig
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
const GhosttyI18n = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Config = @import("Config.zig");
|
||||||
|
const gresource = @import("../apprt/gtk/gresource.zig");
|
||||||
|
|
||||||
|
const domain = "com.mitchellh.ghostty";
|
||||||
|
|
||||||
|
const locales = [_][]const u8{
|
||||||
|
"zh_CN.UTF-8",
|
||||||
|
};
|
||||||
|
|
||||||
|
owner: *std.Build,
|
||||||
|
steps: []*std.Build.Step,
|
||||||
|
|
||||||
|
/// This step updates the translation files on disk that should be
|
||||||
|
/// committed to the repo.
|
||||||
|
update_step: *std.Build.Step,
|
||||||
|
|
||||||
|
pub fn init(b: *std.Build, cfg: *const Config) !GhosttyI18n {
|
||||||
|
var steps = std.ArrayList(*std.Build.Step).init(b.allocator);
|
||||||
|
defer steps.deinit();
|
||||||
|
|
||||||
|
if (cfg.app_runtime == .gtk) {
|
||||||
|
// Output the .mo files used by the GTK apprt
|
||||||
|
inline for (locales) |locale| {
|
||||||
|
const msgfmt = b.addSystemCommand(&.{ "msgfmt", "-o", "-" });
|
||||||
|
msgfmt.addFileArg(b.path("po/" ++ locale ++ ".po"));
|
||||||
|
|
||||||
|
try steps.append(&b.addInstallFile(
|
||||||
|
msgfmt.captureStdOut(),
|
||||||
|
std.fmt.comptimePrint(
|
||||||
|
"share/locale/{s}/LC_MESSAGES/{s}.mo",
|
||||||
|
.{ locale, domain },
|
||||||
|
),
|
||||||
|
).step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.owner = b,
|
||||||
|
.update_step = try createUpdateStep(b),
|
||||||
|
.steps = try steps.toOwnedSlice(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install(self: *const GhosttyI18n) void {
|
||||||
|
for (self.steps) |step| self.owner.getInstallStep().dependOn(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createUpdateStep(b: *std.Build) !*std.Build.Step {
|
||||||
|
const xgettext = b.addSystemCommand(&.{
|
||||||
|
"xgettext",
|
||||||
|
"--language=C", // Silence the "unknown extension" errors
|
||||||
|
"--from-code=UTF-8",
|
||||||
|
"--add-comments=Translators",
|
||||||
|
"--keyword=_",
|
||||||
|
"--keyword=C_:1c,2",
|
||||||
|
"--package-name=" ++ domain,
|
||||||
|
"--msgid-bugs-address=m@mitchellh.com",
|
||||||
|
"--copyright-holder=Mitchell Hashimoto",
|
||||||
|
"-o",
|
||||||
|
"-",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Not cacheable due to the gresource files
|
||||||
|
xgettext.has_side_effects = true;
|
||||||
|
|
||||||
|
inline for (gresource.blueprint_files) |blp| {
|
||||||
|
// We avoid using addFileArg here since the full, absolute file path
|
||||||
|
// would be added to the file as its location, which differs for
|
||||||
|
// everyone's checkout of the repository.
|
||||||
|
// This comes at a cost of losing per-file caching, of course.
|
||||||
|
xgettext.addArg(std.fmt.comptimePrint(
|
||||||
|
"src/apprt/gtk/ui/{[major]}.{[minor]}/{[name]s}.blp",
|
||||||
|
blp,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var gtk_files = try b.build_root.handle.openDir(
|
||||||
|
"src/apprt/gtk",
|
||||||
|
.{ .iterate = true },
|
||||||
|
);
|
||||||
|
defer gtk_files.close();
|
||||||
|
|
||||||
|
var walk = try gtk_files.walk(b.allocator);
|
||||||
|
defer walk.deinit();
|
||||||
|
while (try walk.next()) |src| {
|
||||||
|
switch (src.kind) {
|
||||||
|
.file => if (!std.mem.endsWith(
|
||||||
|
u8,
|
||||||
|
src.basename,
|
||||||
|
".zig",
|
||||||
|
)) continue,
|
||||||
|
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
xgettext.addArg((b.pathJoin(&.{ "src/apprt/gtk", src.path })));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wf = b.addWriteFiles();
|
||||||
|
wf.addCopyFileToSource(
|
||||||
|
xgettext.captureStdOut(),
|
||||||
|
"po/" ++ domain ++ ".pot",
|
||||||
|
);
|
||||||
|
|
||||||
|
inline for (locales) |locale| {
|
||||||
|
const msgmerge = b.addSystemCommand(&.{ "msgmerge", "-q" });
|
||||||
|
msgmerge.addFileArg(b.path("po/" ++ locale ++ ".po"));
|
||||||
|
msgmerge.addFileArg(xgettext.captureStdOut());
|
||||||
|
wf.addCopyFileToSource(msgmerge.captureStdOut(), "po/" ++ locale ++ ".po");
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wf.step;
|
||||||
|
}
|
@ -40,6 +40,7 @@ COPY ./dist/linux /src/dist/linux
|
|||||||
COPY ./images /src/images
|
COPY ./images /src/images
|
||||||
COPY ./include /src/include
|
COPY ./include /src/include
|
||||||
COPY ./pkg /src/pkg
|
COPY ./pkg /src/pkg
|
||||||
|
COPY ./po /src/po
|
||||||
COPY ./nix /src/nix
|
COPY ./nix /src/nix
|
||||||
COPY ./vendor /src/vendor
|
COPY ./vendor /src/vendor
|
||||||
COPY ./build.zig /src/build.zig
|
COPY ./build.zig /src/build.zig
|
||||||
|
@ -13,6 +13,7 @@ pub const GhosttyExe = @import("GhosttyExe.zig");
|
|||||||
pub const GhosttyFrameData = @import("GhosttyFrameData.zig");
|
pub const GhosttyFrameData = @import("GhosttyFrameData.zig");
|
||||||
pub const GhosttyLib = @import("GhosttyLib.zig");
|
pub const GhosttyLib = @import("GhosttyLib.zig");
|
||||||
pub const GhosttyResources = @import("GhosttyResources.zig");
|
pub const GhosttyResources = @import("GhosttyResources.zig");
|
||||||
|
pub const GhosttyI18n = @import("GhosttyI18n.zig");
|
||||||
pub const GhosttyXCFramework = @import("GhosttyXCFramework.zig");
|
pub const GhosttyXCFramework = @import("GhosttyXCFramework.zig");
|
||||||
pub const GhosttyWebdata = @import("GhosttyWebdata.zig");
|
pub const GhosttyWebdata = @import("GhosttyWebdata.zig");
|
||||||
pub const HelpStrings = @import("HelpStrings.zig");
|
pub const HelpStrings = @import("HelpStrings.zig");
|
||||||
|
@ -9,14 +9,22 @@ const Allocator = std.mem.Allocator;
|
|||||||
/// This is highly Ghostty-specific and can likely be generalized at
|
/// This is highly Ghostty-specific and can likely be generalized at
|
||||||
/// some point but we can cross that bridge if we ever need to.
|
/// some point but we can cross that bridge if we ever need to.
|
||||||
pub fn resourcesDir(alloc: std.mem.Allocator) !?[]const u8 {
|
pub fn resourcesDir(alloc: std.mem.Allocator) !?[]const u8 {
|
||||||
// If we have an environment variable set, we always use that.
|
// Use the GHOSTTY_RESOURCES_DIR environment variable in release builds.
|
||||||
|
//
|
||||||
|
// In debug builds we try using terminfo detection first instead, since
|
||||||
|
// if debug Ghostty is launched by an older version of Ghostty, it
|
||||||
|
// would inherit the old, stale resources of older Ghostty instead of the
|
||||||
|
// freshly built ones under zig-out/share/ghostty.
|
||||||
|
//
|
||||||
// Note: we ALWAYS want to allocate here because the result is always
|
// Note: we ALWAYS want to allocate here because the result is always
|
||||||
// freed, do not try to use internal_os.getenv or posix getenv.
|
// freed, do not try to use internal_os.getenv or posix getenv.
|
||||||
if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
|
if (comptime builtin.mode != .Debug) {
|
||||||
if (dir.len > 0) return dir;
|
if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
|
||||||
} else |err| switch (err) {
|
if (dir.len > 0) return dir;
|
||||||
error.EnvironmentVariableNotFound => {},
|
} else |err| switch (err) {
|
||||||
else => return err,
|
error.EnvironmentVariableNotFound => {},
|
||||||
|
else => return err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the sentinel value we look for in the path to know
|
// This is the sentinel value we look for in the path to know
|
||||||
@ -52,6 +60,17 @@ pub fn resourcesDir(alloc: std.mem.Allocator) !?[]const u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If terminfo detection failed in debug builds (somehow),
|
||||||
|
// fallback and use the provided resources dir.
|
||||||
|
if (comptime builtin.mode == .Debug) {
|
||||||
|
if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
|
||||||
|
if (dir.len > 0) return dir;
|
||||||
|
} else |err| switch (err) {
|
||||||
|
error.EnvironmentVariableNotFound => {},
|
||||||
|
else => return err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user