forked from MapComplete/MapComplete
		
	Add singular forms for units
This commit is contained in:
		
							parent
							
								
									c9ba7a8d44
								
							
						
					
					
						commit
						feeca1de46
					
				
					 9 changed files with 338 additions and 95 deletions
				
			
		|  | @ -2,7 +2,7 @@ import {Utils} from "../Utils"; | |||
| 
 | ||||
| export default class Constants { | ||||
| 
 | ||||
|     public static vNumber = "0.9.10"; | ||||
|     public static vNumber = "0.9.11"; | ||||
| 
 | ||||
|     // The user journey states thresholds when a new feature gets unlocked
 | ||||
|     public static userJourney = { | ||||
|  |  | |||
|  | @ -1,13 +1,19 @@ | |||
| import {Translation} from "../UI/i18n/Translation"; | ||||
| import {ApplicableUnitJson} from "./ThemeConfig/Json/UnitConfigJson"; | ||||
| import Translations from "../UI/i18n/Translations"; | ||||
| import {UIEventSource} from "../Logic/UIEventSource"; | ||||
| import BaseUIElement from "../UI/BaseUIElement"; | ||||
| import Toggle from "../UI/Input/Toggle"; | ||||
| 
 | ||||
| export class Denomination { | ||||
|     public readonly canonical: string; | ||||
|     readonly default: boolean; | ||||
|     readonly prefix: boolean; | ||||
|     public readonly _canonicalSingular: string; | ||||
|     public readonly default: boolean; | ||||
|     public readonly prefix: boolean; | ||||
|     public readonly alternativeDenominations: string []; | ||||
|     private readonly _human: Translation; | ||||
|     private readonly _humanSingular?: Translation; | ||||
| 
 | ||||
| 
 | ||||
|     constructor(json: ApplicableUnitJson, context: string) { | ||||
|         context = `${context}.unit(${json.canonicalDenomination})` | ||||
|  | @ -15,6 +21,8 @@ export class Denomination { | |||
|         if (this.canonical === undefined) { | ||||
|             throw `${context}: this unit has no decent canonical value defined` | ||||
|         } | ||||
|         this._canonicalSingular = json.canonicalDenominationSingular?.trim() | ||||
| 
 | ||||
| 
 | ||||
|         json.alternativeDenomination.forEach((v, i) => { | ||||
|             if (((v?.trim() ?? "") === "")) { | ||||
|  | @ -27,6 +35,7 @@ export class Denomination { | |||
|         this.default = json.default ?? false; | ||||
| 
 | ||||
|         this._human = Translations.T(json.human, context + "human") | ||||
|         this._humanSingular = Translations.T(json.humanSingular, context + "humanSingular") | ||||
| 
 | ||||
|         this.prefix = json.prefix ?? false; | ||||
| 
 | ||||
|  | @ -35,7 +44,22 @@ export class Denomination { | |||
|     get human(): Translation { | ||||
|         return this._human.Clone() | ||||
|     } | ||||
| 
 | ||||
|     | ||||
|     get humanSingular(): Translation { | ||||
|         return (this._humanSingular ?? this._human).Clone() | ||||
|     } | ||||
|      | ||||
|     getToggledHuman(isSingular: UIEventSource<boolean>): BaseUIElement{ | ||||
|         if(this._humanSingular === undefined){ | ||||
|             return this.human | ||||
|         } | ||||
|         return new Toggle( | ||||
|             this.humanSingular, | ||||
|             this.human, | ||||
|             isSingular | ||||
|         ) | ||||
|     } | ||||
|      | ||||
|     public canonicalValue(value: string, actAsDefault?: boolean) { | ||||
|         if (value === undefined) { | ||||
|             return undefined; | ||||
|  | @ -44,9 +68,12 @@ export class Denomination { | |||
|         if (stripped === null) { | ||||
|             return null; | ||||
|         } | ||||
|         return (stripped + " " + this.canonical.trim()).trim(); | ||||
|         if(stripped === "1" && this._canonicalSingular !== undefined){ | ||||
|             return "1 "+this._canonicalSingular | ||||
|         } | ||||
|         return stripped + " " + this.canonical; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     /** | ||||
|      * Returns the core value (without unit) if: | ||||
|      * - the value ends with the canonical or an alternative value (or begins with if prefix is set) | ||||
|  | @ -61,26 +88,36 @@ export class Denomination { | |||
|         } | ||||
| 
 | ||||
|         value = value.toLowerCase() | ||||
|         if (this.prefix) { | ||||
|             if (value.startsWith(this.canonical) && this.canonical !== "") { | ||||
|                 return value.substring(this.canonical.length).trim(); | ||||
|             } | ||||
|             for (const alternativeValue of this.alternativeDenominations) { | ||||
|                 if (value.startsWith(alternativeValue)) { | ||||
|                     return value.substring(alternativeValue.length).trim(); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             if (value.endsWith(this.canonical.toLowerCase()) && this.canonical !== "") { | ||||
|                 return value.substring(0, value.length - this.canonical.length).trim(); | ||||
|             } | ||||
|             for (const alternativeValue of this.alternativeDenominations) { | ||||
|                 if (value.endsWith(alternativeValue.toLowerCase())) { | ||||
|                     return value.substring(0, value.length - alternativeValue.length).trim(); | ||||
|                 } | ||||
|         const self = this; | ||||
|         function startsWith(key){ | ||||
|             if(self.prefix){ | ||||
|                 return value.startsWith(key) | ||||
|             }else{ | ||||
|                 return value.endsWith(key) | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         function substr(key){ | ||||
|             if(self.prefix){ | ||||
|                 return value.substr(key.length).trim() | ||||
|             }else{ | ||||
|                 return value.substring(0, value.length - key.length).trim() | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         if(this.canonical !== "" && startsWith(this.canonical.toLowerCase())){ | ||||
|             return substr(this.canonical) | ||||
|         }  | ||||
|          | ||||
|         if(this._canonicalSingular !== undefined && this._canonicalSingular !== "" && startsWith(this._canonicalSingular)){ | ||||
|             return substr(this._canonicalSingular) | ||||
|         } | ||||
|          | ||||
|         for (const alternativeValue of this.alternativeDenominations) { | ||||
|             if (startsWith(alternativeValue)) { | ||||
|                 return substr(alternativeValue); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         if (this.default || actAsDefault) { | ||||
|             const parsed = Number(value.trim()) | ||||
|  |  | |||
|  | @ -24,7 +24,12 @@ export interface ApplicableUnitJson | |||
|      * If the user inputs '42', the canonical value will be added and it'll become '42m' | ||||
|      */ | ||||
|     canonicalDenomination: string, | ||||
| 
 | ||||
|     /** | ||||
|      * The canonical denomination in the case that the unit is precisely '1' | ||||
|      */ | ||||
|     canonicalDenominationSingular?: string, | ||||
|      | ||||
|      | ||||
|     /** | ||||
|      * A list of alternative values which can occur in the OSM database - used for parsing. | ||||
|      */ | ||||
|  | @ -39,6 +44,15 @@ export interface ApplicableUnitJson | |||
|      */ | ||||
|     human?: string | any | ||||
| 
 | ||||
|     /** | ||||
|      * The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g. | ||||
|      * { | ||||
|      *     "en": "minute", | ||||
|      *     "nl": "minuut"x² | ||||
|      * } | ||||
|      */ | ||||
|     humanSingular?: string | any | ||||
| 
 | ||||
|     /** | ||||
|      * If set, then the canonical value will be prefixed instead, e.g. for '€' | ||||
|      * Note that if all values use 'prefix', the dropdown might move to before the text field | ||||
|  |  | |||
|  | @ -34,10 +34,10 @@ export class Unit { | |||
|         this.denominationsSorted = [...this.denominations] | ||||
|         this.denominationsSorted.sort((a, b) => b.canonical.length - a.canonical.length) | ||||
| 
 | ||||
| 
 | ||||
|         const possiblePostFixes = new Set<string>() | ||||
| 
 | ||||
|         function addPostfixesOf(str) { | ||||
|             if(str === undefined){return} | ||||
|             str = str.toLowerCase() | ||||
|             for (let i = 0; i < str.length + 1; i++) { | ||||
|                 const substr = str.substring(0, i) | ||||
|  | @ -47,6 +47,7 @@ export class Unit { | |||
| 
 | ||||
|         for (const denomination of this.denominations) { | ||||
|             addPostfixesOf(denomination.canonical) | ||||
|             addPostfixesOf(denomination._canonicalSingular) | ||||
|             denomination.alternativeDenominations.forEach(addPostfixesOf) | ||||
|         } | ||||
|         this.possiblePostFixes = Array.from(possiblePostFixes) | ||||
|  | @ -111,7 +112,7 @@ export class Unit { | |||
|             return undefined; | ||||
|         } | ||||
|         const [stripped, denom] = this.findDenomination(value) | ||||
|         const human = denom?.human | ||||
|         const human =  stripped === "1" ? denom?.humanSingular :  denom?.human | ||||
|         if (human === undefined) { | ||||
|             return new FixedUiElement(stripped ?? value); | ||||
|         } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue