зеркало из https://github.com/mozilla/gecko-dev.git
Bug 394516 - Figure out a remaining-time rounding scheme for minutes -> hours/days. r=sdwilsh, r=l10n@mozilla.com (Pike), b-ff3=beltzner
This commit is contained in:
Родитель
e72a2e5040
Коммит
390fadeb5d
|
@ -81,6 +81,10 @@ EXPORT_RESOURCE = \
|
|||
$(srcdir)/language.properties \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_JS_MODULES = \
|
||||
PluralForm.jsm \
|
||||
$(NULL)
|
||||
|
||||
# we don't want the shared lib, but we want to force the creation of a static lib.
|
||||
FORCE_STATIC_LIB = 1
|
||||
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Plural Form l10n Code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Edward Lee <edward.lee@engineering.uiuc.edu>.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
EXPORTED_SYMBOLS = [ "PluralForm" ];
|
||||
|
||||
/**
|
||||
* This module provides the PluralForm object which contains a method to figure
|
||||
* out which plural form of a word to use for a given number based on the
|
||||
* current localization.
|
||||
*
|
||||
* List of methods:
|
||||
*
|
||||
* string pluralForm
|
||||
* get(int aNum, string aWords)
|
||||
*/
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
const kIntlProperties = "chrome://global/locale/intl.properties";
|
||||
|
||||
// These are the available plural functions that give the appropriate index
|
||||
// based on the plural rule number specified
|
||||
let gFunctions = [
|
||||
function(n) 0,
|
||||
function(n) n!=1?1:0,
|
||||
function(n) n>1?1:0,
|
||||
function(n) n%10==1&&n%100!=11?1:n!=0?2:0,
|
||||
function(n) n==1?0:n==2?1:2,
|
||||
function(n) n==1?0:n==0||n%100>0&&n%100<20?1:2,
|
||||
function(n) n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1,
|
||||
function(n) n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2,
|
||||
function(n) n==1?0:n>=2&&n<=4?1:2,
|
||||
function(n) n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2,
|
||||
function(n) n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3
|
||||
];
|
||||
|
||||
let PluralForm = {
|
||||
/**
|
||||
* Get the correct plural form of a word based on the number
|
||||
*
|
||||
* @param aNum
|
||||
* The number to decide which plural form to use
|
||||
* @param aWords
|
||||
* A semi-colon (;) separated string of words to pick the plural form
|
||||
* @return The appropriate plural form of the word
|
||||
*/
|
||||
get: (function initGetPluralForm()
|
||||
{
|
||||
// initGetPluralForm gets called right away when this module is loaded and
|
||||
// creates getPluralForm function. The function it creates is based on the
|
||||
// value of pluralRule specified in the intl stringbundle.
|
||||
// See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
|
||||
// Get the plural rule number from the intl stringbundle
|
||||
let ruleNum = Number(Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService).createBundle(kIntlProperties).
|
||||
GetStringFromName("pluralRule"));
|
||||
|
||||
// Default to "all plural" if the value is out of bounds or invalid
|
||||
if (ruleNum < 0 || ruleNum >= gFunctions.length || isNaN(ruleNum)) {
|
||||
log(["Invalid rule number: ", ruleNum, " -- defaulting to 0"]);
|
||||
ruleNum = 0;
|
||||
}
|
||||
|
||||
// Return a function that gets the right plural form
|
||||
let pluralFunc = gFunctions[ruleNum];
|
||||
return function(aNum, aWords) {
|
||||
// Figure out which index to use for the semi-colon separated words
|
||||
let index = pluralFunc(aNum ? Number(aNum) : 0);
|
||||
let words = aWords ? aWords.split(/;/) : [""];
|
||||
|
||||
let ret = words[index];
|
||||
|
||||
// Check for array out of bounds or empty strings
|
||||
if ((ret == undefined) || (ret == "")) {
|
||||
// Display a message in the error console
|
||||
log(["Index #", index, " of '", aWords, "' for value ", aNum,
|
||||
" is invalid -- plural rule #", ruleNum]);
|
||||
|
||||
// Default to the first entry (which might be empty, but not undefined)
|
||||
ret = words[0];
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
})(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Private helper function to log errors to the error console and command line
|
||||
*
|
||||
* @param aMsg
|
||||
* Error message to log or an array of strings to concat
|
||||
*/
|
||||
function log(aMsg)
|
||||
{
|
||||
let msg = "PluralForm.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
|
||||
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
|
||||
logStringMessage(msg);
|
||||
dump(msg + "\n");
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Plural Form l10n Test Code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Edward Lee <edward.lee@engineering.uiuc.edu>.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/**
|
||||
* This unit test makes sure the plural form for the default language (by
|
||||
* development), English, is working for the PluralForm javascript module.
|
||||
*/
|
||||
|
||||
Components.utils.import("resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
function run_test()
|
||||
{
|
||||
// Make sure for good inputs, things work as expected
|
||||
for (var num = 0; num <= 1000; num++)
|
||||
do_check_eq(num == 1 ? "word" : "words", PluralForm.get(num, "word;words"));
|
||||
|
||||
// Not having enough plural forms defaults to the first form
|
||||
do_check_eq("word", PluralForm.get(2, "word"));
|
||||
|
||||
// Empty forms defaults to the first form
|
||||
do_check_eq("word", PluralForm.get(2, "word;"));
|
||||
}
|
|
@ -6,6 +6,13 @@
|
|||
# charset names and use canonical names exactly as listed there.
|
||||
# Also note that "UTF-8" should always be included in intl.charsetmenu.browser.static
|
||||
general.useragent.locale=en-US
|
||||
|
||||
# LOCALIZATION NOTE (pluralRule): Pick the appropriate plural rule for your
|
||||
# language. This will determine how many plural forms of a word you will need
|
||||
# to provide and in what order.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
pluralRule=1
|
||||
|
||||
# Localization Note: font.language.group controls the initial setting of the
|
||||
# language drop-down in the fonts pref panel. Set it to the value of one of the
|
||||
# menuitems in the "selectLangs" menulist in
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
# LOCALIZATION NOTE (seconds, minutes, hours, days): Semi-colon list of plural
|
||||
# forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
seconds=second;seconds
|
||||
minutes=minute;minutes
|
||||
hours=hour;hours
|
||||
days=day;days
|
||||
|
||||
# LOCALIZATION NOTE (paused): — is the "em dash" (long dash)
|
||||
paused=Paused — #1
|
||||
downloading=Downloading
|
||||
|
@ -43,14 +50,17 @@ transferSameUnits=#1 of #3 #4
|
|||
transferDiffUnits=#1 #2 of #3 #4
|
||||
transferNoTotal=#1 #2
|
||||
|
||||
# LOCALIZATION NOTE (timeMinutesLeft): number of minutes left (greater than 1)
|
||||
# LOCALIZATION NOTE (timeSecondsLeft): number of seconds left (greater than 3)
|
||||
# 3 min -> 2 min -> 60 secs -> 59 secs -> … -> 5 secs -> 4 secs -> few secs
|
||||
# examples: 11 minutes left; 11 seconds left;
|
||||
timeMinutesLeft=#1 minutes left
|
||||
timeSecondsLeft=#1 seconds left
|
||||
timeFewSeconds=A few seconds left
|
||||
timeUnknown=Unknown time left
|
||||
# LOCALIZATION NOTE (timePair): #1 time number; #2 time unit
|
||||
# example: 1 minute; 11 hours
|
||||
timePair=#1 #2
|
||||
# LOCALIZATION NOTE (timeLeftSingle): #1 time left
|
||||
# example: 1 minute remaining; 11 hours remaining
|
||||
timeLeftSingle=#1 remaining
|
||||
# LOCALIZATION NOTE (timeLeftDouble): #1 time left; #2 time left sub units
|
||||
# example: 11 hours, 2 minutes remaining; 1 day, 22 hours remaining
|
||||
timeLeftDouble=#1, #2 remaining
|
||||
timeFewSeconds=A few seconds remaining
|
||||
timeUnknown=Unknown time remaining
|
||||
|
||||
# LOCALIZATION NOTE (doneStatus): — is the "em dash" (long dash)
|
||||
# #1 download size for FINISHED or download state; #2 host (e.g., eTLD + 1, IP)
|
||||
|
|
|
@ -58,10 +58,15 @@ EXPORTED_SYMBOLS = [ "DownloadUtils" ];
|
|||
*
|
||||
* [double convertedBytes, string units]
|
||||
* convertByteUnits(int aBytes)
|
||||
*
|
||||
* [int time, string units, int subTime, string subUnits]
|
||||
* convertTimeUnits(double aSecs)
|
||||
*/
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils
|
||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
const kDownloadProperties =
|
||||
"chrome://mozapps/locale/downloads/downloads.properties";
|
||||
|
@ -73,13 +78,16 @@ let gStr = {
|
|||
transferSameUnits: "transferSameUnits",
|
||||
transferDiffUnits: "transferDiffUnits",
|
||||
transferNoTotal: "transferNoTotal",
|
||||
timeMinutesLeft: "timeMinutesLeft",
|
||||
timeSecondsLeft: "timeSecondsLeft",
|
||||
timePair: "timePair",
|
||||
timeLeftSingle: "timeLeftSingle",
|
||||
timeLeftDouble: "timeLeftDouble",
|
||||
timeFewSeconds: "timeFewSeconds",
|
||||
timeUnknown: "timeUnknown",
|
||||
doneScheme: "doneScheme",
|
||||
doneFileScheme: "doneFileScheme",
|
||||
units: ["bytes", "kilobyte", "megabyte", "gigabyte"],
|
||||
// Update timeSize in convertTimeUnits if changing the length of this array
|
||||
timeUnits: ["seconds", "minutes", "hours", "days"],
|
||||
};
|
||||
|
||||
// Convert strings to those in the string bundle
|
||||
|
@ -122,7 +130,7 @@ let DownloadUtils = {
|
|||
|
||||
// Calculate the time remaining if we have valid values
|
||||
let seconds = (aSpeed > 0) && (aMaxBytes > 0) ?
|
||||
Math.ceil((aMaxBytes - aCurrBytes) / aSpeed) : -1;
|
||||
(aMaxBytes - aCurrBytes) / aSpeed : -1;
|
||||
|
||||
// Update the bytes transferred and bytes total
|
||||
let (transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes)) {
|
||||
|
@ -186,7 +194,8 @@ let DownloadUtils = {
|
|||
/**
|
||||
* Generate a "time left" string given an estimate on the time left and the
|
||||
* last time. The extra time is used to give a better estimate on the time to
|
||||
* show.
|
||||
* show. Both the time values are doubles instead of integers to help get
|
||||
* sub-second accuracy for current and future estimates.
|
||||
*
|
||||
* @param aSeconds
|
||||
* Current estimate on number of seconds left for the download
|
||||
|
@ -202,12 +211,22 @@ let DownloadUtils = {
|
|||
if (aSeconds < 0)
|
||||
return [gStr.timeUnknown, aLastSec];
|
||||
|
||||
// Reuse the last seconds if the new one is only slighty longer
|
||||
// This avoids jittering seconds, e.g., 41 40 38 40 -> 41 40 38 38
|
||||
// However, large changes are shown, e.g., 41 38 49 -> 41 38 49
|
||||
let (diff = aSeconds - aLastSec) {
|
||||
if (diff > 0 && diff <= 10)
|
||||
aSeconds = aLastSec;
|
||||
// Apply smoothing only if the new time isn't a huge change -- e.g., if the
|
||||
// new time is more than half the previous time; this is useful for
|
||||
// downloads that start/resume slowly
|
||||
if (aSeconds > aLastSec / 2) {
|
||||
// Apply hysteresis to favor downward over upward swings
|
||||
// 30% of down and 10% of up (exponential smoothing)
|
||||
let (diff = aSeconds - aLastSec) {
|
||||
aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff;
|
||||
}
|
||||
|
||||
// If the new time is similar, reuse something close to the last seconds,
|
||||
// but subtract a little to provide forward progress
|
||||
let diff = aSeconds - aLastSec;
|
||||
let diffPct = diff / aLastSec * 100;
|
||||
if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5)
|
||||
aSeconds = aLastSec - (diff < 0 ? .4 : .2);
|
||||
}
|
||||
|
||||
// Decide what text to show for the time
|
||||
|
@ -215,13 +234,24 @@ let DownloadUtils = {
|
|||
if (aSeconds < 4) {
|
||||
// Be friendly in the last few seconds
|
||||
timeLeft = gStr.timeFewSeconds;
|
||||
} else if (aSeconds <= 60) {
|
||||
// Show 2 digit seconds starting at 60
|
||||
timeLeft = replaceInsert(gStr.timeSecondsLeft, 1, aSeconds);
|
||||
} else {
|
||||
// Show minutes
|
||||
timeLeft = replaceInsert(gStr.timeMinutesLeft, 1,
|
||||
Math.ceil(aSeconds / 60));
|
||||
// Convert the seconds into its two largest units to display
|
||||
let [time1, unit1, time2, unit2] =
|
||||
DownloadUtils.convertTimeUnits(aSeconds);
|
||||
|
||||
let pair1 = replaceInsert(gStr.timePair, 1, time1);
|
||||
pair1 = replaceInsert(pair1, 2, unit1);
|
||||
let pair2 = replaceInsert(gStr.timePair, 1, time2);
|
||||
pair2 = replaceInsert(pair2, 2, unit2);
|
||||
|
||||
// Only show minutes for under 1 hour or the second pair is 0
|
||||
if (aSeconds < 3600 || time2 == 0) {
|
||||
timeLeft = replaceInsert(gStr.timeLeftSingle, 1, pair1);
|
||||
} else {
|
||||
// We've got 2 pairs of times to display
|
||||
timeLeft = replaceInsert(gStr.timeLeftDouble, 1, pair1);
|
||||
timeLeft = replaceInsert(timeLeft, 2, pair2);
|
||||
}
|
||||
}
|
||||
|
||||
return [timeLeft, aSeconds];
|
||||
|
@ -315,8 +345,76 @@ let DownloadUtils = {
|
|||
|
||||
return [aBytes, gStr.units[unitIndex]];
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a number of seconds to the two largest units. Time values are
|
||||
* whole numbers, and units have the correct plural/singular form.
|
||||
*
|
||||
* @param aSecs
|
||||
* Seconds to convert into the appropriate 2 units
|
||||
* @return 4-item array [first value, its unit, second value, its unit]
|
||||
*/
|
||||
convertTimeUnits: function(aSecs)
|
||||
{
|
||||
// These are the maximum values for seconds, minutes, hours corresponding
|
||||
// with gStr.timeUnits without the last item
|
||||
let timeSize = [60, 60, 24];
|
||||
|
||||
let time = aSecs;
|
||||
let scale = 1;
|
||||
let unitIndex = 0;
|
||||
|
||||
// Keep converting to the next unit while we have units left and the
|
||||
// current one isn't the largest unit possible
|
||||
while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) {
|
||||
time /= timeSize[unitIndex];
|
||||
scale *= timeSize[unitIndex];
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
let value = convertTimeUnitsValue(time);
|
||||
let units = convertTimeUnitsUnits(value, unitIndex);
|
||||
|
||||
let extra = aSecs - value * scale;
|
||||
let nextIndex = unitIndex - 1;
|
||||
|
||||
// Convert the extra time to the next largest unit
|
||||
for (let index = 0; index < nextIndex; index++)
|
||||
extra /= timeSize[index];
|
||||
|
||||
let value2 = convertTimeUnitsValue(extra);
|
||||
let units2 = convertTimeUnitsUnits(value2, nextIndex);
|
||||
|
||||
return [value, units, value2, units2];
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Private helper for convertTimeUnits that gets the display value of a time
|
||||
*
|
||||
* @param aTime
|
||||
* Time value for display
|
||||
* @return An integer value for the time rounded down
|
||||
*/
|
||||
function convertTimeUnitsValue(aTime)
|
||||
{
|
||||
return Math.floor(aTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper for convertTimeUnits that gets the display units of a time
|
||||
*
|
||||
* @param aTime
|
||||
* Time value for display
|
||||
* @param aIndex
|
||||
* Index into gStr.timeUnits for the appropriate unit
|
||||
* @return The appropriate plural form of the unit for the time
|
||||
*/
|
||||
function convertTimeUnitsUnits(aTime, aIndex)
|
||||
{
|
||||
return PluralForm.get(aTime, gStr.timeUnits[aIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper function to replace a placeholder string with a real string
|
||||
*
|
||||
|
|
Загрузка…
Ссылка в новой задаче