Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
a0b0d42b
Commit
a0b0d42b
authored
Nov 04, 2013
by
Carsten Brandt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fixed message formatting for all the ICU versions
- refactored replaceNamedArguments. - ensure gridview message works on all systems. fixes #1072
parent
2faf6bb1
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
165 additions
and
55 deletions
+165
-55
MessageFormatter.php
framework/yii/i18n/MessageFormatter.php
+61
-45
MessageFormatterTest.php
tests/unit/framework/i18n/MessageFormatterTest.php
+104
-10
No files found.
framework/yii/i18n/MessageFormatter.php
View file @
a0b0d42b
...
@@ -26,7 +26,7 @@ use yii\base\NotSupportedException;
...
@@ -26,7 +26,7 @@ use yii\base\NotSupportedException;
* to use MessageFormatter features.
* to use MessageFormatter features.
*
*
* The fallback implementation only supports the following message formats:
* The fallback implementation only supports the following message formats:
* - plural formatting for english
* - plural formatting for english
('one' and 'other' selectors)
* - select format
* - select format
* - simple parameters
* - simple parameters
* - integer number parameters
* - integer number parameters
...
@@ -93,13 +93,15 @@ class MessageFormatter extends Component
...
@@ -93,13 +93,15 @@ class MessageFormatter extends Component
}
}
if
(
version_compare
(
PHP_VERSION
,
'5.5.0'
,
'<'
)
||
version_compare
(
INTL_ICU_VERSION
,
'4.8'
,
'<'
))
{
if
(
version_compare
(
PHP_VERSION
,
'5.5.0'
,
'<'
)
||
version_compare
(
INTL_ICU_VERSION
,
'4.8'
,
'<'
))
{
$pattern
=
$this
->
replaceNamedArguments
(
$pattern
,
$params
);
// replace named arguments
$params
=
array_values
(
$params
);
$pattern
=
$this
->
replaceNamedArguments
(
$pattern
,
$params
,
$newParams
);
$params
=
$newParams
;
}
}
$formatter
=
new
\MessageFormatter
(
$language
,
$pattern
);
$formatter
=
new
\MessageFormatter
(
$language
,
$pattern
);
if
(
$formatter
===
null
)
{
if
(
$formatter
===
null
)
{
$this
->
_errorCode
=
-
1
;
$this
->
_errorCode
=
intl_get_error_code
()
;
$this
->
_errorMessage
=
"Message pattern is invalid
."
;
$this
->
_errorMessage
=
"Message pattern is invalid
: "
.
intl_get_error_message
()
;
return
false
;
return
false
;
}
}
$result
=
$formatter
->
format
(
$params
);
$result
=
$formatter
->
format
(
$params
);
...
@@ -136,6 +138,8 @@ class MessageFormatter extends Component
...
@@ -136,6 +138,8 @@ class MessageFormatter extends Component
// replace named arguments
// replace named arguments
if
((
$tokens
=
$this
->
tokenizePattern
(
$pattern
))
===
false
)
{
if
((
$tokens
=
$this
->
tokenizePattern
(
$pattern
))
===
false
)
{
$this
->
_errorCode
=
-
1
;
$this
->
_errorMessage
=
"Message pattern is invalid."
;
return
false
;
return
false
;
}
}
$map
=
[];
$map
=
[];
...
@@ -179,48 +183,49 @@ class MessageFormatter extends Component
...
@@ -179,48 +183,49 @@ class MessageFormatter extends Component
* @param array $args The array of values to insert into the format string.
* @param array $args The array of values to insert into the format string.
* @return string The pattern string with placeholders replaced.
* @return string The pattern string with placeholders replaced.
*/
*/
private
static
function
replaceNamedArguments
(
$pattern
,
$args
)
private
function
replaceNamedArguments
(
$pattern
,
$givenParams
,
&
$resultingParams
,
&
$map
=
[]
)
{
{
$map
=
array_flip
(
array_keys
(
$args
));
if
((
$tokens
=
$this
->
tokenizePattern
(
$pattern
))
===
false
)
{
return
false
;
// parsing pattern based on ICU grammar:
}
// http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
foreach
(
$tokens
as
$i
=>
$token
)
{
$parts
=
explode
(
'{'
,
$pattern
);
if
(
!
is_array
(
$token
))
{
$c
=
count
(
$parts
);
continue
;
$pattern
=
$parts
[
0
];
}
$d
=
0
;
$param
=
trim
(
$token
[
0
]);
$stack
=
[];
if
(
isset
(
$givenParams
[
$param
]))
{
for
(
$i
=
1
;
$i
<
$c
;
$i
++
)
{
// if param is given, replace it with a number
if
(
preg_match
(
'~^(\s*)([\d\w]+)(\s*)([},])(\s*)(.*)$~us'
,
$parts
[
$i
],
$matches
))
{
if
(
!
isset
(
$map
[
$param
]))
{
// if we are not inside a plural or select this is a message
$map
[
$param
]
=
count
(
$map
);
if
(
!
isset
(
$stack
[
$d
])
||
$stack
[
$d
]
!=
'plural'
&&
$stack
[
$d
]
!=
'select'
)
{
// make sure only used params are passed to format method
$d
++
;
$resultingParams
[
$map
[
$param
]]
=
$givenParams
[
$param
];
// replace normal arg if it is available
}
if
(
isset
(
$map
[
$matches
[
2
]]))
{
$token
[
0
]
=
$map
[
$param
];
$q
=
''
;
$quote
=
""
;
$pattern
.=
'{'
.
$matches
[
1
]
.
$map
[
$matches
[
2
]]
.
$matches
[
3
];
}
else
{
}
else
{
// quote unused token
// quote unused args
$quote
=
"'"
;
$q
=
(
$matches
[
4
]
==
'}'
)
?
"'"
:
""
;
}
$pattern
.=
"
$q
{"
.
$matches
[
1
]
.
$matches
[
2
]
.
$matches
[
3
];
$type
=
isset
(
$token
[
1
])
?
trim
(
$token
[
1
])
:
'none'
;
}
// replace plural and select format recursively
$pattern
.=
(
$term
=
$matches
[
4
]
.
$q
.
$matches
[
5
]
.
$matches
[
6
]);
if
(
$type
==
'plural'
||
$type
==
'select'
)
{
// store type of current level
if
(
!
isset
(
$token
[
2
]))
{
$stack
[
$d
]
=
(
$matches
[
4
]
==
','
)
?
substr
(
$matches
[
6
],
0
,
6
)
:
'none'
;
return
false
;
// if it's plural or select, the next bracket is NOT begin of a message then!
}
if
(
$stack
[
$d
]
==
'plural'
||
$stack
[
$d
]
==
'select'
)
{
$subtokens
=
$this
->
tokenizePattern
(
$token
[
2
]);
$i
++
;
$c
=
count
(
$subtokens
);
$d
-=
substr_count
(
$term
,
'}'
);
for
(
$k
=
0
;
$k
+
1
<
$c
;
$k
++
)
{
}
else
{
if
(
is_array
(
$subtokens
[
$k
])
||
!
is_array
(
$subtokens
[
++
$k
]))
{
$d
-=
substr_count
(
$term
,
'}'
);
return
false
;
continue
;
}
}
$subpattern
=
$this
->
replaceNamedArguments
(
implode
(
','
,
$subtokens
[
$k
]),
$givenParams
,
$resultingParams
,
$map
);
$subtokens
[
$k
]
=
$quote
.
'{'
.
$quote
.
$subpattern
.
$quote
.
'}'
.
$quote
;
}
}
$token
[
2
]
=
implode
(
''
,
$subtokens
);
}
}
$pattern
.=
'{'
.
$parts
[
$i
];
$tokens
[
$i
]
=
$quote
.
'{'
.
$quote
.
implode
(
','
,
$token
)
.
$quote
.
'}'
.
$quote
;
$d
+=
1
-
substr_count
(
$parts
[
$i
],
'}'
);
}
}
return
$pattern
;
return
implode
(
''
,
$tokens
)
;
}
}
/**
/**
...
@@ -233,11 +238,15 @@ class MessageFormatter extends Component
...
@@ -233,11 +238,15 @@ class MessageFormatter extends Component
protected
function
fallbackFormat
(
$pattern
,
$args
,
$locale
)
protected
function
fallbackFormat
(
$pattern
,
$args
,
$locale
)
{
{
if
((
$tokens
=
$this
->
tokenizePattern
(
$pattern
))
===
false
)
{
if
((
$tokens
=
$this
->
tokenizePattern
(
$pattern
))
===
false
)
{
$this
->
_errorCode
=
-
1
;
$this
->
_errorMessage
=
"Message pattern is invalid."
;
return
false
;
return
false
;
}
}
foreach
(
$tokens
as
$i
=>
$token
)
{
foreach
(
$tokens
as
$i
=>
$token
)
{
if
(
is_array
(
$token
))
{
if
(
is_array
(
$token
))
{
if
((
$tokens
[
$i
]
=
$this
->
parseToken
(
$token
,
$args
,
$locale
))
===
false
)
{
if
((
$tokens
[
$i
]
=
$this
->
parseToken
(
$token
,
$args
,
$locale
))
===
false
)
{
$this
->
_errorCode
=
-
1
;
$this
->
_errorMessage
=
"Message pattern is invalid."
;
return
false
;
return
false
;
}
}
}
}
...
@@ -296,6 +305,9 @@ class MessageFormatter extends Component
...
@@ -296,6 +305,9 @@ class MessageFormatter extends Component
*/
*/
private
function
parseToken
(
$token
,
$args
,
$locale
)
private
function
parseToken
(
$token
,
$args
,
$locale
)
{
{
// parsing pattern based on ICU grammar:
// http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
$param
=
trim
(
$token
[
0
]);
$param
=
trim
(
$token
[
0
]);
if
(
isset
(
$args
[
$param
]))
{
if
(
isset
(
$args
[
$param
]))
{
$arg
=
$args
[
$param
];
$arg
=
$args
[
$param
];
...
@@ -323,6 +335,9 @@ class MessageFormatter extends Component
...
@@ -323,6 +335,9 @@ class MessageFormatter extends Component
/* http://icu-project.org/apiref/icu4c/classicu_1_1SelectFormat.html
/* http://icu-project.org/apiref/icu4c/classicu_1_1SelectFormat.html
selectStyle = (selector '{' message '}')+
selectStyle = (selector '{' message '}')+
*/
*/
if
(
!
isset
(
$token
[
2
]))
{
return
false
;
}
$select
=
static
::
tokenizePattern
(
$token
[
2
]);
$select
=
static
::
tokenizePattern
(
$token
[
2
]);
$c
=
count
(
$select
);
$c
=
count
(
$select
);
$message
=
false
;
$message
=
false
;
...
@@ -348,6 +363,9 @@ class MessageFormatter extends Component
...
@@ -348,6 +363,9 @@ class MessageFormatter extends Component
keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
message: see MessageFormat
message: see MessageFormat
*/
*/
if
(
!
isset
(
$token
[
2
]))
{
return
false
;
}
$plural
=
static
::
tokenizePattern
(
$token
[
2
]);
$plural
=
static
::
tokenizePattern
(
$token
[
2
]);
$c
=
count
(
$plural
);
$c
=
count
(
$plural
);
$message
=
false
;
$message
=
false
;
...
@@ -363,9 +381,7 @@ class MessageFormatter extends Component
...
@@ -363,9 +381,7 @@ class MessageFormatter extends Component
}
}
if
(
$message
===
false
&&
$selector
==
'other'
||
if
(
$message
===
false
&&
$selector
==
'other'
||
$selector
[
0
]
==
'='
&&
(
int
)
mb_substr
(
$selector
,
1
)
==
$arg
||
$selector
[
0
]
==
'='
&&
(
int
)
mb_substr
(
$selector
,
1
)
==
$arg
||
$selector
==
'zero'
&&
$arg
-
$offset
==
0
||
$selector
==
'one'
&&
$arg
-
$offset
==
1
$selector
==
'one'
&&
$arg
-
$offset
==
1
||
$selector
==
'two'
&&
$arg
-
$offset
==
2
)
{
)
{
$message
=
implode
(
','
,
str_replace
(
'#'
,
$arg
-
$offset
,
$plural
[
$i
]));
$message
=
implode
(
','
,
str_replace
(
'#'
,
$arg
-
$offset
,
$plural
[
$i
]));
}
}
...
...
tests/unit/framework/i18n/MessageFormatterTest.php
View file @
a0b0d42b
...
@@ -22,13 +22,6 @@ class MessageFormatterTest extends TestCase
...
@@ -22,13 +22,6 @@ class MessageFormatterTest extends TestCase
const
SUBJECT
=
'сабж'
;
const
SUBJECT
=
'сабж'
;
const
SUBJECT_VALUE
=
'Answer to the Ultimate Question of Life, the Universe, and Everything'
;
const
SUBJECT_VALUE
=
'Answer to the Ultimate Question of Life, the Universe, and Everything'
;
protected
function
setUp
()
{
if
(
!
extension_loaded
(
"intl"
))
{
$this
->
markTestSkipped
(
"intl not installed. Skipping."
);
}
}
public
function
patterns
()
public
function
patterns
()
{
{
return
[
return
[
...
@@ -76,7 +69,9 @@ _MSG_
...
@@ -76,7 +69,9 @@ _MSG_
'num_guests'
=>
4
,
'num_guests'
=>
4
,
'host'
=>
'ralph'
,
'host'
=>
'ralph'
,
'guest'
=>
'beep'
'guest'
=>
'beep'
]
],
defined
(
'INTL_ICU_VERSION'
)
&&
version_compare
(
INTL_ICU_VERSION
,
'4.8'
,
'<'
),
'select format is available in ICU > 4.4 and plural format with =X selector is avilable since 4.8'
],
],
[
[
...
@@ -86,6 +81,8 @@ _MSG_
...
@@ -86,6 +81,8 @@ _MSG_
'name'
=>
'Alexander'
,
'name'
=>
'Alexander'
,
'gender'
=>
'male'
,
'gender'
=>
'male'
,
],
],
defined
(
'INTL_ICU_VERSION'
)
&&
version_compare
(
INTL_ICU_VERSION
,
'4.4.2'
,
'<'
),
'select format is available in ICU > 4.4'
],
],
// verify pattern in select does not get replaced
// verify pattern in select does not get replaced
...
@@ -99,7 +96,9 @@ _MSG_
...
@@ -99,7 +96,9 @@ _MSG_
'he'
=>
'wtf'
,
'he'
=>
'wtf'
,
'she'
=>
'wtf'
,
'she'
=>
'wtf'
,
'it'
=>
'wtf'
,
'it'
=>
'wtf'
,
]
],
defined
(
'INTL_ICU_VERSION'
)
&&
version_compare
(
INTL_ICU_VERSION
,
'4.4.2'
,
'<'
),
'select format is available in ICU > 4.4'
],
],
// verify pattern in select message gets replaced
// verify pattern in select message gets replaced
...
@@ -112,6 +111,8 @@ _MSG_
...
@@ -112,6 +111,8 @@ _MSG_
'he'
=>
'wtf'
,
'he'
=>
'wtf'
,
'she'
=>
'wtf'
,
'she'
=>
'wtf'
,
],
],
defined
(
'INTL_ICU_VERSION'
)
&&
version_compare
(
INTL_ICU_VERSION
,
'4.8'
,
'<'
),
'parameters in select format do not seem to work in ICU < 4.8'
],
],
// some parser specific verifications
// some parser specific verifications
...
@@ -124,6 +125,92 @@ _MSG_
...
@@ -124,6 +125,92 @@ _MSG_
'he'
=>
'wtf'
,
'he'
=>
'wtf'
,
'she'
=>
'wtf'
,
'she'
=>
'wtf'
,
],
],
defined
(
'INTL_ICU_VERSION'
)
&&
version_compare
(
INTL_ICU_VERSION
,
'4.4.2'
,
'<'
),
'select format is available in ICU > 4.4'
],
// test ICU version compatibility
[
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.'
,
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.'
,
[],
],
[
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.'
,
'Showing <b>1-10</b> of <b>12</b> items.'
,
[
// A
'begin'
=>
1
,
'end'
=>
10
,
'count'
=>
10
,
'totalCount'
=>
12
,
'page'
=>
1
,
'pageCount'
=>
2
,
]
],
[
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.'
,
'Showing <b>1-1</b> of <b>1</b> item.'
,
[
// B
'begin'
=>
1
,
'end'
=>
1
,
'count'
=>
1
,
'totalCount'
=>
1
,
'page'
=>
1
,
'pageCount'
=>
1
,
]
],
[
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.'
,
'Showing <b>0-0</b> of <b>0</b> items.'
,
[
// C
'begin'
=>
0
,
'end'
=>
0
,
'count'
=>
0
,
'totalCount'
=>
0
,
'page'
=>
1
,
'pageCount'
=>
1
,
]
],
[
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.'
,
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.'
,
[]
],
[
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.'
,
'Total <b>1</b> item.'
,
[
'count'
=>
1
,
]
],
[
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.'
,
'Total <b>1</b> item.'
,
[
'begin'
=>
5
,
'count'
=>
1
,
'end'
=>
10
,
]
],
[
'{0, plural, one {offer} other {offers}}'
,
'{0, plural, one {offer} other {offers}}'
,
[],
],
[
'{0, plural, one {offer} other {offers}}'
,
'offers'
,
[
0
],
],
[
'{0, plural, one {offer} other {offers}}'
,
'offer'
,
[
1
],
],
[
'{0, plural, one {offer} other {offers}}'
,
'offers'
,
[
13
],
],
],
];
];
}
}
...
@@ -204,8 +291,11 @@ _MSG_
...
@@ -204,8 +291,11 @@ _MSG_
/**
/**
* @dataProvider patterns
* @dataProvider patterns
*/
*/
public
function
testNamedArguments
(
$pattern
,
$expected
,
$args
)
public
function
testNamedArguments
(
$pattern
,
$expected
,
$args
,
$skip
=
false
,
$skipMessage
=
''
)
{
{
if
(
$skip
)
{
$this
->
markTestSkipped
(
$skipMessage
);
}
$formatter
=
new
MessageFormatter
();
$formatter
=
new
MessageFormatter
();
$result
=
$formatter
->
format
(
$pattern
,
$args
,
'en_US'
);
$result
=
$formatter
->
format
(
$pattern
,
$args
,
'en_US'
);
$this
->
assertEquals
(
$expected
,
$result
,
$formatter
->
getErrorMessage
());
$this
->
assertEquals
(
$expected
,
$result
,
$formatter
->
getErrorMessage
());
...
@@ -216,6 +306,10 @@ _MSG_
...
@@ -216,6 +306,10 @@ _MSG_
*/
*/
public
function
testParseNamedArguments
(
$pattern
,
$expected
,
$args
,
$locale
=
'en_US'
)
public
function
testParseNamedArguments
(
$pattern
,
$expected
,
$args
,
$locale
=
'en_US'
)
{
{
if
(
!
extension_loaded
(
"intl"
))
{
$this
->
markTestSkipped
(
"intl not installed. Skipping."
);
}
$formatter
=
new
MessageFormatter
();
$formatter
=
new
MessageFormatter
();
$result
=
$formatter
->
parse
(
$pattern
,
$expected
,
$locale
);
$result
=
$formatter
->
parse
(
$pattern
,
$expected
,
$locale
);
$this
->
assertEquals
(
$args
,
$result
,
$formatter
->
getErrorMessage
()
.
' Pattern: '
.
$pattern
);
$this
->
assertEquals
(
$args
,
$result
,
$formatter
->
getErrorMessage
()
.
' Pattern: '
.
$pattern
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment