mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
Update the translations document
Summary: Fixes T8616. The rules for contributors have come up a few times recently, so update this document to give more complete advice. Also try to do a better job with "adding new classes" (previously: libphutil libraries blah blah no one cares). Test Plan: Read documents. Reviewers: btrahan, joshuaspence Reviewed By: btrahan, joshuaspence Subscribers: joshuaspence, epriestley Maniphest Tasks: T8616 Differential Revision: https://secure.phabricator.com/D13358
This commit is contained in:
parent
9921cbc41a
commit
85083c88c1
10 changed files with 609 additions and 195 deletions
|
@ -2,7 +2,7 @@
|
|||
"name": "phabcontrib",
|
||||
"title": "Phabricator Contributor Documentation",
|
||||
"short": "Phabricator Contributor Docs",
|
||||
"preface": "Information for Phabricator contributors.",
|
||||
"preface": "Information for Phabricator contributors and developers.",
|
||||
"root": "../../../",
|
||||
"uri.source":
|
||||
"https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "phabdev",
|
||||
"title": "Phabricator Technical Documentation",
|
||||
"short": "Phabricator Tech Docs",
|
||||
"preface": "Technical documentation intended for Phabricator developers.",
|
||||
"preface": "Technical reference material for Phabricator developers.",
|
||||
"root": "../../../",
|
||||
"uri.source":
|
||||
"https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
|
||||
|
|
256
src/docs/contributor/adding_new_classes.diviner
Normal file
256
src/docs/contributor/adding_new_classes.diviner
Normal file
|
@ -0,0 +1,256 @@
|
|||
@title Adding New Classes
|
||||
@group developer
|
||||
|
||||
Guide to adding new classes to extend Phabricator.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Phabricator is highly modular, and many parts of it can be extended by adding
|
||||
new classes. This document explains how to write new classes to change or
|
||||
expand the behavior of Phabricator.
|
||||
|
||||
IMPORTANT: The upstream does not offer support with extension development.
|
||||
|
||||
Fundamentals
|
||||
============
|
||||
|
||||
Phabricator primarily discovers functionality by looking at concrete subclasses
|
||||
of some base class. For example, Phabricator determines which applications are
|
||||
available by looking at all of the subclasses of
|
||||
@{class@phabricator:PhabricatorApplication}. It
|
||||
discovers available workflows in `arc` by looking at all of the subclasses of
|
||||
@{class@arcanist:ArcanistWorkflow}. It discovers available locales
|
||||
by looking at all of the subclasses of @{class@libphutil:PhutilLocale}.
|
||||
|
||||
This pattern holds in many cases, so you can often add functionality by adding
|
||||
new classes with no other work. Phabricator will automatically discover and
|
||||
integrate the new capabilities or features at runtime.
|
||||
|
||||
There are two main ways to add classes:
|
||||
|
||||
- **Extensions Directory**: This is a simple way to add new code. It is
|
||||
less powerful, but takes a lot less work. This is good for quick changes,
|
||||
testing and development, or getting started on a larger project.
|
||||
- **Creating Libraries**: This is a more advanced and powerful way to
|
||||
organize extension code. This is better for larger or longer-lived
|
||||
projects, or any code which you plan to distribute.
|
||||
|
||||
The next sections walk through these approaches in greater detail.
|
||||
|
||||
|
||||
Extensions Directory
|
||||
====================
|
||||
|
||||
The easiest way to extend Phabricator by adding new classes is to drop them
|
||||
into the extensions directory, at `phabricator/src/extensions/`.
|
||||
|
||||
This is intended as a quick way to add small pieces of functionality, test new
|
||||
features, or get started on a larger project. Extending Phabricator like this
|
||||
imposes a small performance penalty compared to using a library.
|
||||
|
||||
This directory exists in all libphutil libraries, so you can find similar
|
||||
directories in `arcanist/src/extensions/` and `libphutil/src/extensions/`.
|
||||
|
||||
For example, to add a new application, create a file like this one and add it
|
||||
to `phabricator/src/extensions/`.
|
||||
|
||||
```name=phabricator/src/extensions/ExampleApplication.php, lang=php
|
||||
<?php
|
||||
|
||||
final class ExampleApplication extends PhabricatorApplication {
|
||||
|
||||
public function getName() {
|
||||
return pht('Example');
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
If you load {nav Applications} in the web UI, you should now see your new
|
||||
application in the list. It won't do anything yet since you haven't defined
|
||||
any interesting behavior, but this is the basic building block of Phabricator
|
||||
extensions.
|
||||
|
||||
|
||||
Creating Libraries
|
||||
==================
|
||||
|
||||
A more powerful (but more complicated) way to extend Phabricator is to create
|
||||
a libphutil library. Libraries can organize a larger amount of code, are easier
|
||||
to work with and distribute, and have slightly better performance than loose
|
||||
source files in the extensions directory.
|
||||
|
||||
In general, you'll perform these one-time setup steps to create a library:
|
||||
|
||||
- Create a new directory.
|
||||
- Use `arc liberate` to initialize and name the library.
|
||||
- Configure Phabricator or Arcanist to load the library.
|
||||
|
||||
Then, to add new code, you do this:
|
||||
|
||||
- Write or update classes.
|
||||
- Update the library metadata by running `arc liberate` again.
|
||||
|
||||
Initializing a Library
|
||||
======================
|
||||
|
||||
To create a new libphutil library, create a directory for it and run
|
||||
`arc liberate` on the directory. This documentation will use a conventional
|
||||
directory layout, which is recommended, but you are free to deviate from this.
|
||||
|
||||
```
|
||||
$ mkdir libcustom/
|
||||
$ cd libcustom/
|
||||
libcustom/ $ arc liberate src/
|
||||
```
|
||||
|
||||
Now you'll get a prompt like this:
|
||||
|
||||
```lang=txt
|
||||
No library currently exists at that path...
|
||||
The directory '/some/path/libcustom/src' does not exist.
|
||||
|
||||
Do you want to create it? [y/N] y
|
||||
Creating new libphutil library in '/some/path/libcustom/src'.
|
||||
Choose a name for the new library.
|
||||
|
||||
What do you want to name this library?
|
||||
```
|
||||
|
||||
Choose a library name (in this case, "libcustom" would be appropriate) and it
|
||||
you should get some details about the library initialization:
|
||||
|
||||
```lang=txt
|
||||
Writing '__phutil_library_init__.php' to
|
||||
'/some/path/libcustom/src/__phutil_library_init__.php'...
|
||||
Using library root at 'src'...
|
||||
Mapping library...
|
||||
Verifying library...
|
||||
Finalizing library map...
|
||||
OKAY Library updated.
|
||||
```
|
||||
|
||||
This will write three files:
|
||||
|
||||
- `src/.phutil_module_cache` This is a cache which makes "arc liberate"
|
||||
faster when you run it to update the library. You can safely remove it at
|
||||
any time. If you check your library into version control, you can add this
|
||||
file to ignore rules (like `.gitignore`).
|
||||
- `src/__phutil_library_init__.php` This records the name of the library and
|
||||
tells libphutil that a library exists here.
|
||||
- `src/__phutil_library_map__.php` This is a map of all the symbols
|
||||
(functions and classes) in the library, which allows them to be autoloaded
|
||||
at runtime and dependencies to be statically managed by `arc liberate`.
|
||||
|
||||
Linking with Phabricator
|
||||
========================
|
||||
|
||||
If you aren't using this library with Phabricator (e.g., you are only using it
|
||||
with Arcanist or are building something else on libphutil) you can skip this
|
||||
step.
|
||||
|
||||
But, if you intend to use this library with Phabricator, you need to define its
|
||||
dependency on Phabricator by creating a `.arcconfig` file which points at
|
||||
Phabricator. For example, you might write this file to
|
||||
`libcustom/.arcconfig`:
|
||||
|
||||
```lang=json
|
||||
{
|
||||
"load": [
|
||||
"phabricator/src/"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For details on creating a `.arcconfig`, see
|
||||
@{article:Arcanist User Guide: Configuring a New Project}. In general, this
|
||||
tells `arc liberate` that it should look for symbols in Phabricator when
|
||||
performing static analysis.
|
||||
|
||||
NOTE: If Phabricator isn't located next to your custom library, specify a
|
||||
path which actually points to the `phabricator/` directory.
|
||||
|
||||
You do not need to declare dependencies on `arcanist` or `libphutil`,
|
||||
since `arc liberate` automatically loads them.
|
||||
|
||||
Finally, edit your Phabricator config to tell it to load your library at
|
||||
runtime, by adding it to `load-libraries`:
|
||||
|
||||
```lang=json
|
||||
...
|
||||
'load-libraries' => array(
|
||||
'libcustom' => 'libcustom/src/',
|
||||
),
|
||||
...
|
||||
```
|
||||
|
||||
Now, Phabricator will be able to load classes from your custom library.
|
||||
|
||||
|
||||
Writing Classes
|
||||
===============
|
||||
|
||||
To actually write classes, create a new module and put code in it:
|
||||
|
||||
libcustom/ $ mkdir src/example/
|
||||
libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
|
||||
|
||||
Now, run `arc liberate` to regenerate the static resource map:
|
||||
|
||||
libcustom/ $ arc liberate src/
|
||||
|
||||
This will automatically regenerate the static map of the library.
|
||||
|
||||
|
||||
What You Can Extend And Invoke
|
||||
==============================
|
||||
|
||||
libphutil, Arcanist and Phabricator are strict about extensibility of classes
|
||||
and visibility of methods and properties. Most classes are marked `final`, and
|
||||
methods have the minimum required visibility (protected or private). The goal
|
||||
of this strictness is to make it clear what you can safely extend, access, and
|
||||
invoke, so your code will keep working as the upstream changes.
|
||||
|
||||
IMPORTANT: We'll still break APIs frequently. The upstream does not support
|
||||
extension development, and none of these APIs are stable.
|
||||
|
||||
When developing libraries to work with libphutil, Arcanist and Phabricator, you
|
||||
should respect method and property visibility.
|
||||
|
||||
If you want to add features but can't figure out how to do it without changing
|
||||
Phabricator code, here are some approaches you may be able to take:
|
||||
|
||||
- {icon check, color=green} **Use Composition**: If possible, use composition
|
||||
rather than extension to build your feature.
|
||||
- {icon check, color=green} **Find Another Approach**: Check the
|
||||
documentation for a better way to accomplish what you're trying to do.
|
||||
- {icon check, color=green} **File a Feature Request**: Let us know what your
|
||||
use case is so we can make the class tree more flexible or configurable, or
|
||||
point you at the right way to do whatever you're trying to do, or explain
|
||||
why we don't let you do it. Note that we **do not support** extension
|
||||
development so you may have mixed luck with this one.
|
||||
|
||||
These approaches are **discouraged**, but also possible:
|
||||
|
||||
- {icon times, color=red} **Fork**: Create an ad-hoc local fork and remove
|
||||
`final` in your copy of the code. This will make it more difficult for you
|
||||
to upgrade in the future, although it may be the only real way forward
|
||||
depending on what you're trying to do.
|
||||
- {icon times, color=red} **Use Reflection**: You can use
|
||||
[[ http://php.net/manual/en/book.reflection.php | Reflection ]] to remove
|
||||
modifiers at runtime. This is fragile and discouraged, but technically
|
||||
possible.
|
||||
- {icon times, color=red} **Remove Modifiers**: Send us a patch removing
|
||||
`final` (or turning `protected` or `private` into `public`). We will almost
|
||||
never accept these patches unless there's a very good reason that the
|
||||
current behavior is wrong.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Continue by:
|
||||
|
||||
- visiting the [[ https://secure.phabricator.com/w/community_resources/ |
|
||||
Community Resources ]] page to find or share extensions and libraries.
|
|
@ -9,57 +9,370 @@ Overview
|
|||
Phabricator partially supports internationalization, but many of the tools
|
||||
are missing or in a prototype state.
|
||||
|
||||
This document very briefly summarizes some of what exists today.
|
||||
This document describes what tools exist today, how to add new translations,
|
||||
and how to use the translation tools to make a codebase translatable.
|
||||
|
||||
|
||||
Adding a New Locale
|
||||
===================
|
||||
|
||||
To add a new locale, subclass @{class:PhutilLocale}. This allows you to
|
||||
introduce a new locale, like "German" or "Klingon".
|
||||
|
||||
Once you've created a locale, applications can add translations for that
|
||||
locale.
|
||||
|
||||
For instructions on adding new classes, see @{article:Adding New Classes}.
|
||||
|
||||
|
||||
Adding Translations to Locale
|
||||
=============================
|
||||
|
||||
To translate strings, subclass @{class:PhutilTranslation}. Translations need
|
||||
to belong to a locale: the locale defines an available language, and each
|
||||
translation subclass provides strings for it.
|
||||
|
||||
Translations are separated from locales so that third-party applications can
|
||||
provide translations into different locales without needing to define those
|
||||
locales themselves.
|
||||
|
||||
For instructions on adding new classes, see @{article:Adding New Classes}.
|
||||
|
||||
|
||||
Writing Translatable Code
|
||||
========
|
||||
=========================
|
||||
|
||||
Strings are marked for translation with @{function@libphutil:pht}.
|
||||
|
||||
Adding a New Locale
|
||||
=========
|
||||
The `pht()` function takes a string (and possibly some parameters) and returns
|
||||
the translated version of that string in the current viewer's locale, if a
|
||||
translation is available.
|
||||
|
||||
To add a new locale, subclass @{class:PhutilLocale}.
|
||||
If text strings will ultimately be read by humans, they should essentially
|
||||
always be wrapped in `pht()`. For example:
|
||||
|
||||
Translating Strings
|
||||
========
|
||||
```lang=php
|
||||
$dialog->appendParagraph(pht('This is an example.'));
|
||||
```
|
||||
|
||||
This allows the code to return the correct Spanish or German or Russian
|
||||
version of the text, if the viewer is using Phabricator in one of those
|
||||
languages and a translation is available.
|
||||
|
||||
Using `pht()` properly so that strings are translatable can be tricky. Briefly,
|
||||
the major rules are:
|
||||
|
||||
- Only pass static strings as the first parameter to `pht()`.
|
||||
- Use parameters to create strings containing user names, object names, etc.
|
||||
- Translate full sentences, not sentence fragments.
|
||||
- Let the translation framework handle plural rules.
|
||||
- Use @{class@libphutil:PhutilNumber} for numbers.
|
||||
- Let the translation framework handle subject gender rules.
|
||||
- Translate all human-readable text, even exceptions and error messages.
|
||||
|
||||
See the next few sections for details on these rules.
|
||||
|
||||
|
||||
Use Static Strings
|
||||
==================
|
||||
|
||||
The first parameter to `pht()` must always be a static string. Broadly, this
|
||||
means it should not contain variables or function or method calls (it's OK to
|
||||
split it across multiple lines and concatenate the parts together).
|
||||
|
||||
These are good:
|
||||
|
||||
```lang=php
|
||||
pht('The night is dark.');
|
||||
pht(
|
||||
'Two roads diverged in a yellow wood, '.
|
||||
'and sorry I could not travel both '.
|
||||
'and be one traveler, long I stood.');
|
||||
|
||||
```
|
||||
|
||||
These won't work (they might appear to work, but are wrong):
|
||||
|
||||
```lang=php, counterexample
|
||||
pht(some_function());
|
||||
pht('The duck says, '.$quack);
|
||||
pht($string);
|
||||
```
|
||||
|
||||
The first argument must be a static string so it can be extracted by static
|
||||
analysis tools and dumped in a big file for translators. If it contains
|
||||
functions or variables, it can't be extracted, so translators won't be able to
|
||||
translate it.
|
||||
|
||||
Lint will warn you about problems with use of static strings in calls to
|
||||
`pht()`.
|
||||
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
You can provide parameters to a translation string by using `sprintf()`-style
|
||||
patterns in the input string. For example:
|
||||
|
||||
```lang=php
|
||||
pht('%s earned an award.', $actor);
|
||||
pht('%s closed %s.', $actor, $task);
|
||||
```
|
||||
|
||||
This is primarily appropriate for usernames, object names, counts, and
|
||||
untranslatable strings like URIs or instructions to run commands from the CLI.
|
||||
|
||||
Parameters normally should not be used to combine two pieces of translated
|
||||
text: see the next section for guidance.
|
||||
|
||||
Sentence Fragments
|
||||
==================
|
||||
|
||||
You should almost always pass the largest block of text to `pht()` that you
|
||||
can. Particularly, it's important to pass complete sentences, not try to build
|
||||
a translation by stringing together sentence fragments.
|
||||
|
||||
There are several reasons for this:
|
||||
|
||||
- It gives translators more context, so they can be more confident they are
|
||||
producing a satisfying, natural-sounding translation which will make sense
|
||||
and sound good to native speakers.
|
||||
- In some languages, one fragment may need to translate differently depending
|
||||
on what the other fragment says.
|
||||
- In some languages, the most natural-sounding translation may change the
|
||||
order of words in the sentence.
|
||||
|
||||
For example, suppose we want to translate these sentence to give the user some
|
||||
instructions about how to use an interface:
|
||||
|
||||
> Turn the switch to the right.
|
||||
|
||||
> Turn the switch to the left.
|
||||
|
||||
> Turn the dial to the right.
|
||||
|
||||
> Turn the dial to the left.
|
||||
|
||||
Maybe we have a function like this:
|
||||
|
||||
```
|
||||
function get_string($is_switch, $is_right) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
One way to write the function body would be like this:
|
||||
|
||||
```lang=php, counterexample
|
||||
$what = $is_switch ? pht('switch') : pht('dial');
|
||||
$dir = $is_right ? pht('right') : pht('left');
|
||||
|
||||
return pht('Turn the ').$what.pht(' to the ').$dir.pht('.');
|
||||
```
|
||||
|
||||
This will work fine in English, but won't work well in other languages.
|
||||
|
||||
One problem with doing this is handling gendered nouns. Languages like Spanish
|
||||
have gendered nouns, where some nouns are "masculine" and others are
|
||||
"feminine". The gender of a noun affects which article (in English, the word
|
||||
"the" is an article) should be used with it.
|
||||
|
||||
In English, we say "**the** knob" and "**the** switch", but a Spanish speaker
|
||||
would say "**la** perilla" and "**el** interruptor", because the noun for
|
||||
"knob" in Spanish is feminine (so it is used with the article "la") while the
|
||||
noun for "switch" is masculine (so it is used with the article "el").
|
||||
|
||||
A Spanish speaker can not translate the string "Turn the" correctly without
|
||||
knowing which gender the noun has. Spanish has //two// translations for this
|
||||
string ("Gira el", "Gira la"), and the form depends on which noun is being
|
||||
used.
|
||||
|
||||
Another problem is that this reduces flexibility. Translating fragments like
|
||||
this locks translators into a specific word order, when rearranging the words
|
||||
might make the sentence sound much more natural to a native speaker.
|
||||
|
||||
For example, if the string read "The knob, to the right, turn it.", it
|
||||
would technically be English and most English readers would understand the
|
||||
meaning, but no native English speaker would speak or write like this.
|
||||
|
||||
However, some languages have different subject-verb order rules or
|
||||
colloquisalisms, and a word order which transliterates like this may sound more
|
||||
natural to a native speaker. By translating fragments instead of complete
|
||||
sentences, you lock translators into English word order.
|
||||
|
||||
Finally, the last fragment is just a period. If a translator is presented with
|
||||
this string in an interface without much context, they have no hope of guessing
|
||||
how it is used in the software (it could be an end-of-sentence marker, or a
|
||||
decimal point, or a date separator, or a currency separator, all of which have
|
||||
very different translations in many locales). It will also conflict with all
|
||||
other translations of the same string in the codebase, so even if they are
|
||||
given context they can't translate it without technical problems.
|
||||
|
||||
To avoid these issues, provide complete sentences for translation. This almost
|
||||
always takes the form of writing out alternatives in full. This is a good way
|
||||
to implement the example function:
|
||||
|
||||
```lang=php
|
||||
if ($is_switch) {
|
||||
if ($is_right) {
|
||||
return pht('Turn the switch to the right.');
|
||||
} else {
|
||||
return pht('Turn the switch to the left.');
|
||||
}
|
||||
} else {
|
||||
if ($is_right) {
|
||||
return pht('Turn the dial to the right.');
|
||||
} else {
|
||||
return pht('Turn the dial to the left.');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Although this is more verbose, translators can now get genders correct,
|
||||
rearrange word order, and have far more context when translating. This enables
|
||||
better, natural-sounding translations which are more satisfying to native
|
||||
speakers.
|
||||
|
||||
To translate strings, subclass @{class:PhutilTranslation}.
|
||||
|
||||
Singular and Plural
|
||||
========
|
||||
===================
|
||||
|
||||
Different languages have various rules for using singular and plural. All you
|
||||
need to do is to call @{function@libphutil:pht} with a text that is suitable for
|
||||
both forms. Example:
|
||||
Different languages have various rules for plural nouns.
|
||||
|
||||
pht('%d beer(s)', $count);
|
||||
In English there are usually two plural noun forms: for one thing, and any
|
||||
other number of things. For example, we say that one chair is a "chair" and any
|
||||
other number of chairs are "chairs": "0 chairs", "1 chair", "2 chairs", etc.
|
||||
|
||||
Translators will translate this text for all different forms the language uses:
|
||||
In other languages, there are different (and, in some cases, more) plural
|
||||
forms. For example, in Czech, there are separate forms for "one", "several",
|
||||
and "many".
|
||||
|
||||
// English translation
|
||||
array('%d beer', '%d beers');
|
||||
Because plural noun rules depend on the language, you should not write code
|
||||
which hard-codes English rules. For example, this won't translate well:
|
||||
|
||||
// Czech translation
|
||||
array('%d pivo', '%d piva', '%d piv');
|
||||
```lang=php, counterexample
|
||||
if ($count == 1) {
|
||||
return pht('This will take an hour.');
|
||||
} else {
|
||||
return pht('This will take hours.');
|
||||
}
|
||||
```
|
||||
|
||||
The ugly identifier passed to @{function@libphutil:pht} will remain in the text
|
||||
only if the translation doesn't exist.
|
||||
This code is hard-coding the English rule for plural nouns. In languages like
|
||||
Czech, the correct word for "hours" may be different if the count is 2 or 15,
|
||||
but a translator won't be able to provide the correct translation if the string
|
||||
is written like this.
|
||||
|
||||
Instead, pass a generic string to the translation engine which //includes// the
|
||||
number of objects, and let it handle plural nouns. This is the correct way to
|
||||
write the translation:
|
||||
|
||||
```lang=php
|
||||
return pht('This will take %s hour(s).', new PhutilNumber($count));
|
||||
```
|
||||
|
||||
If you now load the web UI, you'll see "hour(s)" literally in the UI. To fix
|
||||
this so the translation sounds better in English, provide translations for this
|
||||
string in the @{class@phabricator:PhabricatorUSEnglishTranslation} file:
|
||||
|
||||
```lang=php
|
||||
'This will take %s hour(s).' => array(
|
||||
'This will take an hour.',
|
||||
'This will take hours.',
|
||||
),
|
||||
```
|
||||
|
||||
The string will then sound natural in English, but non-English translators will
|
||||
also be able to produce a natural translation.
|
||||
|
||||
Note that the translations don't actually include the number in this case. The
|
||||
number is being passed from the code, but that just lets the translation engine
|
||||
get the rules right: the number does not need to appear in the final
|
||||
translations shown to the user.
|
||||
|
||||
Using PhutilNumber
|
||||
==================
|
||||
|
||||
When translating numbers, you should almost always use `%s` and wrap the count
|
||||
or number in `new PhutilNumber($count)`. For example:
|
||||
|
||||
```lang=php
|
||||
pht('You have %s experience point(s).', new PhutilNumber($xp));
|
||||
```
|
||||
|
||||
This will let the translation engine handle plural noun rules correctly, and
|
||||
also format large numbers correctly in a locale-aware way with proper unit and
|
||||
decimal separators (for example, `1000000` may be printed as "1,000,000",
|
||||
with commas for readability).
|
||||
|
||||
The exception to this rule is IDs which should not be written with unit
|
||||
separators. For example, this is correct for an object ID:
|
||||
|
||||
```lang=php
|
||||
pht('This diff has ID %d.', $diff->getID());
|
||||
```
|
||||
|
||||
Male and Female
|
||||
========
|
||||
===============
|
||||
|
||||
Different languages use different words for talking about males, females and
|
||||
unknown genders. Callsites have to call @{function@libphutil:pht} passing
|
||||
@{class:PhabricatorUser} (or other implementation of
|
||||
@{interface@libphutil:PhutilPerson}) if talking about the user. Example:
|
||||
Different languages also use different words for talking about subjects who are
|
||||
male, female or have an unknown gender. In English this is mostly just
|
||||
pronouns (like "he" and "she") but there are more complex rules in other
|
||||
languages, and languages like Czech also require verb agreement.
|
||||
|
||||
pht('%s wrote', $actor);
|
||||
When a parameter refers to a gendered person, pass an object which implements
|
||||
@{interface@libphutil:PhutilPerson} to `pht()` so translators can provide
|
||||
gendered translation variants.
|
||||
|
||||
Translators will create this translations:
|
||||
```lang=php
|
||||
pht('%s wrote', $actor);
|
||||
```
|
||||
|
||||
// English translation
|
||||
'%s wrote';
|
||||
Translators will create these translations:
|
||||
|
||||
// Czech translation
|
||||
array('%s napsal', '%s napsala');
|
||||
```lang=php
|
||||
// English translation
|
||||
'%s wrote';
|
||||
|
||||
// Czech translation
|
||||
array('%s napsal', '%s napsala');
|
||||
```
|
||||
|
||||
(You usually don't need to worry very much about this rule, it is difficult to
|
||||
get wrong in standard code.)
|
||||
|
||||
|
||||
Exceptions and Errors
|
||||
=====================
|
||||
|
||||
You should translate all human-readable text, even exceptions and error
|
||||
messages. This is primarily a rule of convenience which is straightforward
|
||||
and easy to follow, not a technical rule.
|
||||
|
||||
Some exceptions and error messages don't //technically// need to be translated,
|
||||
as they will never be shown to a user, but many exceptions and error messages
|
||||
are (or will become) user-facing on some way. When writing a message, there is
|
||||
often no clear and objective way to determine which type of message you are
|
||||
writing. Rather than try to distinguish which are which, we simply translate
|
||||
all human-readable text. This rule is unambiguous and easy to follow.
|
||||
|
||||
In cases where similar error or exception text is often repeated, it is
|
||||
probably appropriate to define an exception for that category of error rather
|
||||
than write the text out repeatedly, anyway. Two examples are
|
||||
@{class@libphutil:PhutilInvalidStateException} and
|
||||
@{class@libphutil:PhutilMethodNotImplementedException}, which mostly exist to
|
||||
produce a consistent message about a common error state in a convenient way.
|
||||
|
||||
There are a handful of error strings in the codebase which may be used before
|
||||
the translation framework is loaded, or may be used during handling other
|
||||
errors, possibly rasised from within the translation framework. This handful
|
||||
of special cases are left untranslated to prevent fatals and cycles in the
|
||||
error handler.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Continue by:
|
||||
|
||||
- adding a new locale or translation file with @{article:Adding New Classes}.
|
||||
|
|
|
@ -207,5 +207,5 @@ integrations, see the base class for your application and
|
|||
Continue by:
|
||||
|
||||
- learning more about extending Phabricator with custom code in
|
||||
@{article:libphutil Libraries User Guide};
|
||||
@{article@contributor:Adding New Classes};
|
||||
- or returning to the @{article: Configuration Guide}.
|
||||
|
|
|
@ -109,7 +109,7 @@ This daemon will daemonize and run normally.
|
|||
just those started with `phd start`. If you're writing a restart script,
|
||||
have it launch any custom daemons explicitly after `phd restart`.
|
||||
- You can write your own daemons and manage them with `phd` by extending
|
||||
@{class:PhabricatorDaemon}. See @{article:libphutil Libraries User Guide}.
|
||||
@{class:PhabricatorDaemon}. See {article@contributor:Adding New Classes}.
|
||||
- See @{article:Diffusion User Guide} for details about tuning the repository
|
||||
daemon.
|
||||
|
||||
|
@ -137,4 +137,4 @@ Continue by:
|
|||
|
||||
- learning about the repository daemon with @{article:Diffusion User Guide};
|
||||
or
|
||||
- writing your own daemons with @{article:libphutil Libraries User Guide}.
|
||||
- writing your own daemons with {article@contributor:Adding New Classes}.
|
||||
|
|
|
@ -38,7 +38,7 @@ make this work, you need to do three things:
|
|||
|
||||
If you haven't created a library for the class to live in yet, you need to do
|
||||
that first. Follow the instructions in
|
||||
@{article:libphutil Libraries User Guide}, then make the library loadable by
|
||||
@{article@contributor:Adding New Classes}, then make the library loadable by
|
||||
adding it to your `.arcconfig` like this:
|
||||
|
||||
{
|
||||
|
|
|
@ -47,7 +47,7 @@ Other options include:
|
|||
|
||||
- **load**: list of additional Phutil libraries to load at startup.
|
||||
See below for details about path resolution, or see
|
||||
@{article:libphutil Libraries User Guide} for a general introduction to
|
||||
@{article@contributor:Adding New Classes} for a general introduction to
|
||||
libphutil libraries.
|
||||
- **https.cabundle**: specifies the path to an alternate certificate bundle
|
||||
for use when making HTTPS connections.
|
||||
|
|
|
@ -21,7 +21,7 @@ To install event listeners in Phabricator, follow these steps:
|
|||
|
||||
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
|
||||
- Add it to a libphutil library, or create a new library (for instructions,
|
||||
see @{article:libphutil Libraries User Guide}.
|
||||
see @{article@contributor:Adding New Classes}.
|
||||
- Configure Phabricator to load the library by adding it to `load-libraries`
|
||||
in the Phabricator config.
|
||||
- Configure Phabricator to install the event listener by adding the class
|
||||
|
@ -38,7 +38,7 @@ To install event listeners in Arcanist, follow these steps:
|
|||
|
||||
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
|
||||
- Add it to a libphutil library, or create a new library (for instructions,
|
||||
see @{article:libphutil Libraries User Guide}.
|
||||
see @{article@contributor:Adding New Classes}.
|
||||
- Configure Phabricator to load the library by adding it to `load`
|
||||
in the Arcanist config (e.g., `.arcconfig`, or user/global config).
|
||||
- Configure Arcanist to install the event listener by adding the class
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
@title libphutil Libraries User Guide
|
||||
@group userguide
|
||||
|
||||
Guide to creating and managing libphutil libraries.
|
||||
|
||||
= Overview =
|
||||
|
||||
libphutil includes a library system which organizes PHP classes and functions
|
||||
into modules. Some extensions and customizations of Arcanist and Phabricator
|
||||
require you to make code available to Phabricator by providing it in a libphutil
|
||||
library.
|
||||
|
||||
For example, if you want to store files in some kind of custom storage engine,
|
||||
you need to write a class which can interact with that engine and then tell
|
||||
Phabricator to load it.
|
||||
|
||||
In general, you perform these one-time setup steps:
|
||||
|
||||
- Create a new directory.
|
||||
- Use `arc liberate` to initialize and name the library.
|
||||
- Add a dependency on Phabricator if necessary.
|
||||
- Add the library to your Phabricator config or `.arcconfig` so it will be
|
||||
loaded at runtime.
|
||||
|
||||
Then, to add new code, you do this:
|
||||
|
||||
- Write or update classes.
|
||||
- Update the library metadata by running `arc liberate` again.
|
||||
|
||||
= Creating a New Library =
|
||||
|
||||
To **create a new libphutil library**:
|
||||
|
||||
$ mkdir libcustom/
|
||||
$ cd libcustom/
|
||||
libcustom/ $ arc liberate src/
|
||||
|
||||
Now you'll get a prompt like this:
|
||||
|
||||
lang=txt
|
||||
No library currently exists at that path...
|
||||
The directory '/some/path/libcustom/src' does not exist.
|
||||
|
||||
Do you want to create it? [y/N] y
|
||||
Creating new libphutil library in '/some/path/libcustom/src'.
|
||||
Choose a name for the new library.
|
||||
|
||||
What do you want to name this library?
|
||||
|
||||
Choose a library name (in this case, "libcustom" would be appropriate) and it
|
||||
you should get some details about the library initialization:
|
||||
|
||||
lang=txt
|
||||
Writing '__phutil_library_init__.php' to
|
||||
'/some/path/libcustom/src/__phutil_library_init__.php'...
|
||||
Using library root at 'src'...
|
||||
Mapping library...
|
||||
Verifying library...
|
||||
Finalizing library map...
|
||||
OKAY Library updated.
|
||||
|
||||
This will write three files:
|
||||
|
||||
- `src/.phutil_module_cache` This is a cache which makes "arc liberate"
|
||||
faster when you run it to update the library. You can safely remove it at
|
||||
any time. If you check your library into version control, you can add this
|
||||
file to ignore rules (like .gitignore).
|
||||
- `src/__phutil_library_init__.php` This records the name of the library and
|
||||
tells libphutil that a library exists here.
|
||||
- `src/__phutil_library_map__.php` This is a map of all the symbols
|
||||
(functions and classes) in the library, which allows them to be autoloaded
|
||||
at runtime and dependencies to be statically managed by "arc liberate".
|
||||
|
||||
= Linking with Phabricator =
|
||||
|
||||
If you aren't using this library with Phabricator (e.g., you are only using it
|
||||
with Arcanist or are building something else on libphutil) you can skip this
|
||||
step.
|
||||
|
||||
But, if you intend to use this library with Phabricator, you need to define its
|
||||
dependency on Phabricator by creating a `.arcconfig` file which points at
|
||||
Phabricator. For example, you might write this file to
|
||||
`libcustom/.arcconfig`:
|
||||
|
||||
{
|
||||
"load": [
|
||||
"phabricator/src/"
|
||||
]
|
||||
}
|
||||
|
||||
For details on creating a `.arcconfig`, see
|
||||
@{article:Arcanist User Guide: Configuring a New Project}. In general, this
|
||||
tells `arc liberate` that it should look for symbols in Phabricator when
|
||||
performing static analysis.
|
||||
|
||||
NOTE: If Phabricator isn't located next to your custom library, specify a
|
||||
path which actually points to the `phabricator/` directory.
|
||||
|
||||
You do not need to declare dependencies on `arcanist` or `libphutil`,
|
||||
since `arc liberate` automatically loads them.
|
||||
|
||||
Finally, edit your Phabricator config to tell it to load your library at
|
||||
runtime, by adding it to `load-libraries`:
|
||||
|
||||
...
|
||||
'load-libraries' => array(
|
||||
'libcustom' => 'libcustom/src/',
|
||||
),
|
||||
...
|
||||
|
||||
Now, Phabricator will be able to load classes from your custom library.
|
||||
|
||||
= Writing Classes =
|
||||
|
||||
To actually write classes, create a new module and put code in it:
|
||||
|
||||
libcustom/ $ mkdir src/example/
|
||||
libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
|
||||
|
||||
Now, run `arc liberate` to regenerate the static resource map:
|
||||
|
||||
libcustom/ $ arc liberate src/
|
||||
|
||||
This will automatically regenerate the static map of the library.
|
||||
|
||||
= What You Can Extend And Invoke =
|
||||
|
||||
libphutil, Arcanist and Phabricator are strict about extensibility of classes
|
||||
and visibility of methods and properties. Most classes are marked `final`, and
|
||||
methods have the minimum required visibility (protected or private). The goal of
|
||||
this strictness is to make it clear what you can safely extend, access, and
|
||||
invoke, so your code will keep working as the upstream changes.
|
||||
|
||||
When developing libraries to work with libphutil, Arcanist and Phabricator, you
|
||||
should respect method and property visibility and extend only classes marked
|
||||
`@stable`. They are rendered with a large callout in the documentation (for
|
||||
example: @{class@libphutil:AbstractDirectedGraph}). These classes are external
|
||||
interfaces intended for extension.
|
||||
|
||||
If you want to extend a class but it is not marked `@stable`, here are some
|
||||
approaches you can take:
|
||||
|
||||
- Good: If possible, use composition rather than extension to build your
|
||||
feature.
|
||||
- Good: Check the documentation for a better way to accomplish what you're
|
||||
trying to do.
|
||||
- Good: Let us know what your use case is so we can make the class tree more
|
||||
flexible or configurable, or point you at the right way to do whatever
|
||||
you're trying to do, or explain why we don't let you do it.
|
||||
- Discouraged: Send us a patch removing "final" (or turning "protected" or
|
||||
"private" into "public"). We generally will not accept these patches, unless
|
||||
there's a good reason that the current behavior is wrong.
|
||||
- Discouraged: Create an ad-hoc local fork and remove "final" in your copy of
|
||||
the code. This will make it more difficult for you to upgrade in the future.
|
||||
- Discouraged: Use Reflection to violate visibility keywords.
|
Loading…
Reference in a new issue