Skip to content
Snippets Groups Projects
Commit 80b3404a authored by Sean Madsen's avatar Sean Madsen
Browse files

Improve language and markdown

parent 5f5b81df
No related branches found
No related tags found
No related merge requests found
......@@ -101,15 +101,15 @@ Changing these can be quite difficult and can break interfaces consumed by downs
## Localization
Any string that will be displayed to the user should be wrapped in ts() to translate the string:
Any string that will be displayed to the user should be wrapped in `ts()` to translate the string:
```
```php
$string = ts("Hello, world!");
```
Translation strings can also include placeholders for variables:
```
```php
$string = ts("Membership for %1 has been updated. The membership End Date is %2.", array(
1 => $userDisplayName,
2 => $endDate,
......
......@@ -7,15 +7,23 @@ We try to reduce the number of strings that will be practically never seen by ad
For example, in `CRM_Upgrade_Incremental_php_FourSeven`, `addTask()` task names such as "Upgrade DB to ..." should be translated:
```php
$this->addTask(ts('Upgrade DB to %1: SQL', array(1 => '4.3.5')), 'task_4_3_x_runSql', $rev);
$this->addTask(
ts('Upgrade DB to %1: SQL', array(1 => '4.3.5')),
'task_4_3_x_runSql',
$rev
);
```
Very specific one-time tasks should not be translated (wrapped in "ts"). Administrators are very unlikely to see such strings. If they do, they will probably need the original English string in order to get support on the forums. They are also strings that are very hard to translate because of lack of context.
Very specific one-time tasks should not be translated (wrapped in `ts`). Administrators are very unlikely to see such strings. If they do, they will probably need the original English string in order to get support on the forums. They are also strings that are very hard to translate because of lack of context.
For example, do not translate:
```php
$this->addTask('Update financial_account_id in financial_trxn table', 'updateFinancialTrxnData', $rev);
$this->addTask(
'Update financial_account_id in financial_trxn table',
'updateFinancialTrxnData',
$rev
);
```
## Localized fields
......@@ -33,15 +41,15 @@ However, since localizable fields add a certain technical complexity, the follow
While there are many cities where street names can officially be in multiple languages (or have official transliterations), users usually enter their address only in one language. It is rarely required to store the address translation (one exception: event locations, which is currently a known limitation).
Similarly, the first and last name of an individual may be written in different alphabets (ex: Latin and Cyrillic), but this is not a frequent use-case worth the complexity. Adminstrators can workaround this by creating custom fields.
Similarly, the first and last name of an individual may be written in different alphabets (ex: Latin and Cyrillic), but this is not a frequent use-case worth the complexity. Administrators can workaround this by creating custom fields.
In order to define a field as localizable, the XML schema files must have the following tag:
In order to define a field as localizable, the [XML schema definition](/framework/database/schema-definition.md) for that field must have the following tag:
```
<localizable>true</localizable>
```
If a new field was not initially tagged as localizable, the upgrade must explicitely convert the field. See the section below on localised fields schema changes.
If a new field was not initially tagged as localizable, the upgrade must explicitly convert the field. See the section below on localised fields schema changes.
## SQL upgrades
......@@ -52,62 +60,99 @@ SQL upgrades must account for two use-cases:
There are two variables exposed to the sql templates when upgrading: `$multilingual` makes it possible to test if the database is multi-lingual, while `$locales` lists the enabled languages. For example:
```
```smarty
{if $multilingual}
{foreach from=$locales item=locale}
UPDATE civicrm_option_group SET label_{$locale} = description_{$locale} WHERE label_{$locale} IS NULL;
UPDATE civicrm_option_group
SET label_{$locale} = description_{$locale}
WHERE label_{$locale} IS NULL;
{/foreach}
{else}
UPDATE civicrm_option_group SET `label` = `description` WHERE `label` IS NULL;
UPDATE civicrm_option_group
SET `label` = `description`
WHERE `label` IS NULL;
{/if}
```
However the `{localize}` helper for SQL upgrades (e.g. statements in `CRM/Upgrade/Incremental/sql/*.mysql.tpl` files) allows you do the same thing without explicitly looping on locales. This UPDATE statement handles both multi-lingual and non-multi-lingual cases.
However the `{localize}` helper for SQL upgrades (e.g. statements in `CRM/Upgrade/Incremental/sql/*.mysql.tpl` files) allows you do the same thing without explicitly looping on locales. This `UPDATE` statement handles both multi-lingual and non-multi-lingual cases.
```
UPDATE `civicrm_premiums` SET {localize field="premiums_nothankyou_label"}premiums_nothankyou_label = '{ts escape="sql"}No thank-you{/ts}'{/localize};
```smarty
UPDATE `civicrm_premiums`
SET
{localize field="premiums_nothankyou_label"}
premiums_nothankyou_label = '{ts escape="sql"}No thank-you{/ts}'
{/localize};
```
On a multi-lingual site with English and French enabled, this would evaluate to:
```
UPDATE `civicrm_premiums` SET premiums_nothankyou_label_en_US = 'No thank-you', premiums_nothankyou_label_fr_FR = 'Non merci';
```
The `{ts}` tag translates the string based on the default language that is set WHEN THE UPGRADE IS BEING RUN. In the above example if the upgrade was run while the default language was French, that column would be set to "Merci non". It would be good to fix this so that the values for each enabled language are translated when a translated string is available.
For an INSERT example, the following query:
```
INSERT INTO civicrm_option_value
(option_group_id, {localize field='label'}label{/localize}, value, name, filter, weight, is_active )
VALUES
(@option_group_id_ere, {localize}'{ts escape="sql"}Participant Role{/ts}'{/localize}, 1, 'participant_role', 0, 1, 1 );
```
On a multi-lingual site with English and French enabled, would evaluate to:
```
INSERT INTO civicrm_option_value
(option_group_id, label_en_US, label_fr_FR, value, name, filter, weight, is_active )
VALUES
(@option_group_id_ere, 'Participant Role', 'Rôle du participant', 1, 'participant_role', 0, 1, 1 );
```sql
UPDATE `civicrm_premiums`
SET
premiums_nothankyou_label_en_US = 'No thank-you',
premiums_nothankyou_label_fr_FR = 'Non merci';
```
The `{ts}` tag translates the string based on the default language that is set _when the upgrade is being run_. In the above example if the upgrade was run while the default language was French, that column would be set to "Merci non". It would be good to fix this so that the values for each enabled language are translated when a translated string is available.
For an `INSERT` example, the following query:
```smarty
INSERT INTO civicrm_option_value (
option_group_id,
{localize field='label'}label{/localize},
value,
name,
filter,
weight,
is_active )
VALUES (
@option_group_id_ere,
{localize}'{ts escape="sql"}Participant Role{/ts}'{/localize},
1,
'participant_role',
0,
1,
1 );
```
On a multi-lingual site with English and French enabled, the above would evaluate to:
```sql
INSERT INTO civicrm_option_value (
option_group_id,
label_en_US,
label_fr_FR,
value,
name,
filter,
weight,
is_active )
VALUES (
@option_group_id_ere,
'Participant Role',
'Rôle du participant',
1,
'participant_role',
0,
1,
1 );
```
## Localised fields schema changes
Two use-cases:
1- An existing field in CiviCRM was not tagged in the xml schema as `<localizable>` (ex: the `title` in `civicrm_survey`, before CiviCRM 4.5). After adding the `<localize>` tag in the XML file, you must also add an upgrade snippet for exsting databases. Example, from sql/4.1.0.mysql.tpl:
1. An existing field in CiviCRM was not tagged in the [XML schema](/framework/database/schema-definition.md) as `<localizable>` (ex: the `title` in `civicrm_survey`, before CiviCRM 4.5). After adding the `<localize>` tag in the XML file, you must also add an upgrade snippet for existing databases. Example, from `sql/4.1.0.mysql.tpl`:
```
{if $multilingual}
{foreach from=$locales item=locale}
ALTER TABLE civicrm_pcp_block ADD link_text_{$locale} varchar(255);
UPDATE civicrm_pcp_block SET link_text_{$locale} = link_text;
{/foreach}
ALTER TABLE civicrm_pcp_block DROP link_text;
{/if}
```
```smarty
{if $multilingual}
{foreach from=$locales item=locale}
ALTER TABLE civicrm_pcp_block ADD link_text_{$locale} varchar(255);
UPDATE civicrm_pcp_block SET link_text_{$locale} = link_text;
{/foreach}
ALTER TABLE civicrm_pcp_block DROP link_text;
{/if}
```
2- A localized field was removed or added, the schema during the upgrade does odd things to figure out which fields are mutli-lingual. Rebuilding the multi-lingual schema will check in CRM/Core/I18n/SchemaStructure.php for the fields used by the database views. If the schema is changed, copy the SchemaStructure.php from the master branch to, for example, SchemaStructure_4_5_alpha1.php. The 4.5 alpha1 will then read this file when rebuilding the schema, see CRM/Core/I18n/Schema.php for more information (getLatestSchema). i.e. during an upgrade, we may be upgrading from 4.0 to 4.5, and when rebuilding the views at each stage, we need to load the correct schema version. Since we do not have a schema file for each minor version, CiviCRM will attempt to load the most relevant schema version to the version of the upgrade step being run.
2. If a localized field was removed or added, the schema does odd things during the upgrade to figure out which fields are mutli-lingual. Rebuilding the multi-lingual schema will check in `CRM/Core/I18n/SchemaStructure.php` for the fields used by the database views. If the schema is changed, copy the `SchemaStructure.php` from the master branch to, for example, `SchemaStructure_4_5_alpha1.php`. The 4.5 alpha1 will then read this file when rebuilding the schema, see `CRM/Core/I18n/Schema.php` for more information (`getLatestSchema`). i.e. during an upgrade, we may be upgrading from 4.0 to 4.5, and when rebuilding the views at each stage, we need to load the correct schema version. Since we do not have a schema file for each minor version, CiviCRM will attempt to load the most relevant schema version to the version of the upgrade step being run.
# Extensions Translation
For developing a CiviCRM extension in a way that can be translated, all the best practices described in the Internationalisation for Developers page apply. This page describes special considerations that need to be taken in count for achieving this.
For developing a CiviCRM extension in a way that can be translated, all the best practices described in the [Internationalisation for Developers](/translation/index.md) page apply. This page describes special considerations that need to be taken into account for extensions.
## For translators: Translating strings on Transifex
There is a separate project on Transifex to translate extensions. Each extension has its own "resource". Therefore, when a translator joins a translation team, he/she can translate all extensions. We didn't see a need to separate each extension in a separate project, because each extension should have only one translation (.po) file.
There is a separate project on Transifex to translate extensions. Each extension has its own "resource". Therefore, when a translator joins a translation team, they can translate all extensions. We didn't see a need to separate each extension in a separate project, because each extension should have only one translation (`.po`) file.
See: https://www.transifex.com/projects/p/civicrm_extensions/
See: <https://www.transifex.com/projects/p/civicrm_extensions/>
## For administrators: Download translation files for extensions
The easiest way to download translations for extensions is to use the [l10nupdate](https://github.com/cividesk/com.cividesk.l10n.update/) extension.
## For developers: Correct usage of the ts() function
## For developers: Correct usage of the `ts()` function
In PHP, Smarty, and JS code, the convention is to perform translations using the `ts()` helper function. This is the same as in core code with the additional requirement that one must specify the "domain" so that the translation engine can use the correct dictionary (.mo file) at run-time.
In PHP, Smarty, and JS code, the convention is to perform translations using the `ts()` helper function. This is the same as in core code &mdash; with the additional requirement that one must specify the "domain" so that the translation engine can use the correct dictionary (`.mo` file) at run-time.
PHP:
......@@ -27,7 +27,7 @@ $string = ts('Hello, %1', array(
Smarty templates:
```
```smarty
{crmScope extensionKey='org.example.myextension'}
<p>{ts 1=$display_name}Hello, %1{/ts}</p>
{/crmScope}
......@@ -51,6 +51,6 @@ $scope.ts = CRM.ts('org.example.myextension');
Angular HTML templates:
```
```html
<p>{{ts('Hello, %1', {1: display_name})}}</p>
```
......@@ -2,11 +2,11 @@
When writing new application code, developers should organize their code in a way which is amenable to internationalization, so that it may be localized to various languages and regions of the world.
If you are an extension developer, there is additional documentation in the [Extension translation](/translation/extensions.md] page.
If you are an extension developer, there is additional documentation in the [Extension translation](/translation/extensions.md) page.
## PHP
The strings hard-coded into PHP should be wrapped in ts() function calls. Here are a few examples:
The strings hard-coded into PHP should be wrapped in `ts()` function calls. Here are a few examples:
```php
$string = ts('Hello, World!');
......@@ -74,7 +74,7 @@ else {
## Javascript
When translating strings in an extension, ts scope needs to be declared. The CRM.ts function takes scope as an argument and returns a function that always applies that scope to ts calls:
When translating strings in an extension, ts scope needs to be declared. The `CRM.ts` function takes scope as an argument and returns a function that always applies that scope to ts calls:
```js
// This closure gets a local copy of jQuery, Lo-dash, and ts
......@@ -83,37 +83,43 @@ When translating strings in an extension, ts scope needs to be declared. The CRM
})(CRM.$, CRM._, CRM.ts('foo.bar.myextension'));
```
Note that `CRM.ts` is not the same as the global `ts` function. `CRM.ts` is a function that returns a function (javascript is wacky like that). Since your closure gives the local `ts` the same name as the global `ts`, it will be used instead.
!!! note
`CRM.ts` is not the same as the global `ts` function. `CRM.ts` is a function that returns a function (javascript is wacky like that). Since your closure gives the local `ts` the same name as the global `ts`, it will be used instead.
Important: Your local version of `ts` could be named anything, but strings in your javascript file cannot be accurately parsed unless you name it ts.
!!! important
Your local version of `ts` could be named anything, but strings in your javascript file cannot be accurately parsed unless you name it `ts`.
## Smarty templates
The strings hard-coded into templates should be wrapped in `{ts}...{/ts}` tags. For example:
* The strings hard-coded into templates should be wrapped in `{ts}...{/ts}` tags. For example:
```
{ts}Full or partial name (last name, or first name, or organization name).{/ts}
```
```smarty
{ts}Full or partial name (last name, or first name, or organization name).{/ts}
```
If you need to pass a variable to the localisable string, you should use the following pattern:
* If you need to pass a variable to the localizable string, you should use the following pattern:
```
<div class="status">{ts 1=$delName}Are you sure you want to delete <b>%1</b> Tag?{/ts}</div>
```
```smarty
<div class="status">
{ts 1=$delName}Are you sure you want to delete <b>%1</b> Tag?{/ts}
</div>
```
When possible, avoid HTML formatting and newlines inside `{ts}...{/ts}` tags. For example:
* When possible, avoid HTML formatting and newlines inside `{ts}...{/ts}` tags.
```
<p>{ts}Hello, world!{/ts}</p>
```
and not:
* Good
```
{ts}<p>This is a bad example.</p>{/ts}
```
```smarty
<p>{ts}Hello, world!{/ts}</p>
```
* Bad
```
{ts}<p>Hello, world!</p>{/ts}
```
## Why not use the Drupal locale module?
## Rationale for using Gettext
In most projects, strings are typically translated by either:
......@@ -127,6 +133,6 @@ In order to be support Joomla!, WordPress, Backdrop and eventually other content
Here are the guides to other popular projects:
* Drupal: https://www.drupal.org/node/322729
* Joomla!: https://docs.joomla.org/Specification_of_language_files
* WordPress: https://codex.wordpress.org/I18n_for_WordPress_Developers
* Drupal: <https://www.drupal.org/node/322729>
* Joomla!: <https://docs.joomla.org/Specification_of_language_files>
* WordPress: <https://codex.wordpress.org/I18n_for_WordPress_Developers>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment