├── LICENSE ├── README.md ├── budget.gif ├── css └── main.css ├── favicon.png ├── index.htm ├── js ├── en.json ├── es.json ├── main.js ├── pt-br.json └── zh-cn.json ├── manifest.appcache └── manifest.json /LICENSE: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # 2018-10-14:v1.0 3 | 4 | CACHE: 5 | favicon.png 6 | index.htm 7 | css/main.css 8 | js/main.js 9 | js/pt-br.json 10 | js/en.json 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Budget 2 | 3 | #### A smart budgeting app that that predicts the location of the expense being made. 4 | 5 | Budget is a JavaScript toy app that implements a [KNN](https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm) to predict where the expense is being made. 6 | 7 | ![](budget.gif) 8 | 9 | An online version is hosted [here](https://victorribeiro.com/budget/). You can add it to your phone as an app, if you like. 10 | 11 | It uses the day of the week, the hour of the day and the value of the expense as features to predict the location of the purchase. 12 | Its a toy app because it stores data in the localStorage of the browser. Its not reliable or robust and the code is a bit messy; it was made as an exercise (or proof of concept) after a machine learning class. 13 | 14 | The app was written in portuguese, but a quick hack was made to accept other languages. Right now you can choose between english and Brazilian portuguese. 15 | 16 | ## How to Translate 17 | 18 | Make a copy of the en.json file and replace the sentences, translating to the desired language. After that, change the variable `langSetting` in js/main.js to the name of the json file you made: 19 | 20 | ```javascript 21 | let langSetting = 'js/en.json'; 22 | ``` 23 | 24 | [![Donations](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=victorqribeiro%40gmail%2ecom&lc=BR&item_name=Victor%20Ribeiro&item_number=donation¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) 25 | -------------------------------------------------------------------------------- /budget.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorqribeiro/budget/73d9e359ffda909e2617583f5ad6b797d37263f5/budget.gif -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | html,body{ 2 | padding:0; 3 | margin:0; 4 | text-align:center; 5 | font-family: Arial; 6 | height:100%; 7 | -webkit-touch-callout: none; 8 | -webkit-user-select: none; 9 | -khtml-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | } 14 | input, select{ 15 | height:3em; 16 | font-size:1em; 17 | } 18 | input[type=text],input[type=number],input[type=password]{ 19 | text-align:center; 20 | } 21 | input[type=button]{ 22 | min-width:90px; 23 | } 24 | input[type=checkbox]{ 25 | display:inline; 26 | height:1.1em; 27 | font-size:1.1em; 28 | } 29 | #abalancamento{ 30 | display:none; 31 | } 32 | #menu{ 33 | padding:10px; 34 | } 35 | #apagar{ 36 | display:none; 37 | } 38 | .divapagarbotao{ 39 | padding-top:10px; 40 | } 41 | .data{ 42 | display:table; 43 | text-align:left; 44 | width:90%; 45 | margin: 0 auto; 46 | background-color:rgb(200,200,200); 47 | padding:5px; 48 | } 49 | .despesa{ 50 | display:table; 51 | text-align:right; 52 | width:90%; 53 | margin: 0 auto; 54 | } 55 | #despesacell1{ 56 | display:table-cell; 57 | width:10%; 58 | } 59 | #despesacell2{ 60 | display:table-cell; 61 | width:20%; 62 | } 63 | #despesacell3{ 64 | display:table-cell; 65 | width:30%; 66 | } 67 | #despesacell4{ 68 | display:table-cell; 69 | width:50%; 70 | } 71 | .lockinput{ 72 | min-width:200px!important; 73 | } 74 | .row{ 75 | padding-bottom:10px; 76 | } 77 | .valordia{ 78 | float:right; 79 | } 80 | .valormes{ 81 | display:inline; 82 | } 83 | .aba{ 84 | display:none; 85 | } 86 | .conteudo{ 87 | max-height:65vH; 88 | height:60vH; 89 | overflow:auto; 90 | border:solid 1px rgb(200,200,200); 91 | border-left:0; 92 | border-right:0; 93 | padding-top:2px; 94 | padding-bottom:2px; 95 | } 96 | .categoriarow{ 97 | width:90%; 98 | max-width: 300px; 99 | text-align:left; 100 | margin: auto auto; 101 | } 102 | #alerta{ 103 | position: absolute; 104 | width: 300px; 105 | height: 200px; 106 | z-index: 9999; 107 | top: 50%; 108 | left: 50%; 109 | margin: -100px 0 0 -150px; 110 | background: rgb(200,200,200); 111 | border:solid 1px rgb(128,128,128); 112 | display:none; 113 | } 114 | #alertamsg{ 115 | margin:20px; 116 | } 117 | #confirma{ 118 | position: absolute; 119 | width: 300px; 120 | height: 200px; 121 | z-index: 9999; 122 | top: 50%; 123 | left: 50%; 124 | margin: -100px 0 0 -150px; 125 | background: rgb(200,200,200); 126 | border:solid 1px rgb(128,128,128); 127 | display:none; 128 | } 129 | #confirmamsg{ 130 | margin:20px; 131 | } 132 | #back{ 133 | position:absolute; 134 | top:0; 135 | left:0; 136 | width:100vW; 137 | height:100vH; 138 | display:none; 139 | z-index:9997; 140 | } 141 | #configPanel{ 142 | width: 50%; 143 | min-width: 100px; 144 | max-width: 250px; 145 | margin: auto auto; 146 | } 147 | .btn{ 148 | display: block; 149 | width:100%; 150 | } 151 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorqribeiro/budget/73d9e359ffda909e2617583f5ad6b797d37263f5/favicon.png -------------------------------------------------------------------------------- /index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Budget 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 20 |
21 |
22 |
23 | 24 |
25 |
26 | 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | 65 |
66 |
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /js/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "en", 3 | "menu" : { 4 | "expense": "Expenses", 5 | "newExpense" : "New", 6 | "settings" : "Settings", 7 | "categories" : "Categories", 8 | "category" : "Category", 9 | "reset" : "Reset", 10 | "erase" : "Delete" 11 | }, 12 | "day" : [ 13 | "Sunday", 14 | "Monday", 15 | "Tuesday", 16 | "Wednesday", 17 | "Thursday", 18 | "Friday", 19 | "Saturday" ], 20 | "month" : [ 21 | "January", 22 | "February", 23 | "March", 24 | "April", 25 | "may", 26 | "June", 27 | "July", 28 | "August", 29 | "September", 30 | "October", 31 | "November", 32 | "December" 33 | ], 34 | "money" : "Us$", 35 | "catExists" : "Category already exists!", 36 | "invalidData" : "Invalid input!", 37 | "defaultCat" : "Market;Gas Station;Snacks;Store", 38 | "moneyInvalid" : "Invalid value! e.g: valid: 105.50", 39 | "noneExpanse" : "No expenses!", 40 | "reset" : "Reseted!", 41 | "confirm" : "Do you want to procced?", 42 | "accept" : "Continue", 43 | "cancel" : "Cancel" 44 | } 45 | -------------------------------------------------------------------------------- /js/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "es", 3 | "menu" : { 4 | "expense": "Gastos", 5 | "newExpense" : "Nuevo", 6 | "settings" : "Configuración", 7 | "categories" : "Categorías", 8 | "category" : "Categoría", 9 | "reset" : "Borrar Data", 10 | "erase" : "Eliminar" 11 | }, 12 | "day" : [ 13 | "Domingo", 14 | "Lunes", 15 | "Martes", 16 | "Miércoles", 17 | "Jueves", 18 | "Viernes", 19 | "Sábado" 20 | ], 21 | "month" : [ 22 | "Enero", 23 | "Febrero", 24 | "Marzo", 25 | "Abril", 26 | "Mayo", 27 | "Junio", 28 | "Julio", 29 | "Agosto", 30 | "Septiembre", 31 | "Octubre", 32 | "Noviembre", 33 | "Diciembre" 34 | ], 35 | "money" : "$", 36 | "catExists" : "La categoría ya existe!", 37 | "invalidData" : "!El valor introducido es inválido!", 38 | "defaultCat" : "Supermercado;Gasolinería;Bocadillos;Tienda", 39 | "moneyInvalid" : "!Valor inválido! ejemplo: válido: 105.50", 40 | "noneExpanse" : "!No hay gastos!", 41 | "reset" : "!Reiniciar!", 42 | "confirm" : "Desea continuar?", 43 | "accept" : "Continuar", 44 | "cancel" : "Cancelar" 45 | } 46 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | let langSetting = 'js/en.json', lang; 2 | 3 | function init(){ 4 | dias = lang.day 5 | meses = lang.month; 6 | money = lang.money; 7 | data = new Date(); 8 | diasemana = data.getDay(); 9 | dia = data.getDate(); 10 | mes = parseInt(data.getMonth())+1; 11 | ano = data.getFullYear(); 12 | hora = data.getHours(); 13 | minuto = data.getMinutes(); 14 | if(localStorage.ultimoid){ 15 | id = localStorage.getItem("ultimoid"); 16 | }else{ 17 | id = 1; 18 | } 19 | if(lang.id !== "pt-br") 20 | traduzGUI(); 21 | recuperaDespesa(); 22 | recuperaCategorias(); 23 | navegacao("abadespesas"); 24 | overwriteForms(); 25 | } 26 | function traduzGUI(){ 27 | document.getElementById("despesa").value = lang.menu.expense; 28 | document.getElementById("lancamento").value = lang.menu.newExpense; 29 | document.getElementById("config").value = lang.menu.settings; 30 | document.getElementById("categoriasbtn").value = lang.menu.categories; 31 | document.getElementById("categoriainput").placeholder = lang.menu.category; 32 | document.getElementById("resetarbtn").value = lang.menu.reset; 33 | document.getElementById("apagarbotao").value = lang.menu.erase; 34 | document.getElementById("apagarcategoria").value = lang.menu.erase; 35 | document.getElementById("cancelbotao").value = lang.cancel; 36 | document.getElementById("confirmabotao").value = lang.accept; 37 | } 38 | function overwriteForms(){ 39 | document.getElementById("despesaform").addEventListener("submit", function(event){ 40 | event.preventDefault(); 41 | lancaDespesa(); 42 | }); 43 | document.getElementById("confirmaform").addEventListener("submit", function(event){ 44 | event.preventDefault(); 45 | document.getElementById("confirmabotao").click(); 46 | }); 47 | document.getElementById("categoriaform").addEventListener("submit", function(event){ 48 | event.preventDefault(); 49 | document.getElementById("adicionacategoria").click(); 50 | }); 51 | } 52 | function gravaCategorias(click=true){ 53 | if(click){ 54 | if(document.getElementById("categoriainput").value.match(/^[0-9a-zA-Z]+$/)){ 55 | cat = localStorage.getItem("categorias").toLowerCase().split(';'); 56 | v = document.getElementById("categoriainput").value; 57 | if( cat.indexOf(v.toLowerCase()) != -1 ){ 58 | alerta("show",lang.catExists); 59 | document.getElementById("categoriainput").value = ""; 60 | return; 61 | } 62 | newCat = localStorage.getItem("categorias")+";"+document.getElementById("categoriainput").value; 63 | localStorage.setItem("categorias",newCat); 64 | }else{ 65 | alerta("show",lang.invalidData); 66 | document.getElementById("categoriainput").value = ""; 67 | return; 68 | } 69 | }else{ 70 | cat = document.getElementById("categorias").innerText.replace(/\n/g,';').replace(/;$/,''); 71 | localStorage.setItem("categorias",cat); 72 | } 73 | recuperaCategorias(); 74 | document.getElementById("categoriainput").value = ""; 75 | } 76 | function removeCategorias(confirmado=false){ 77 | abreConfirma("show", removeCategorias); 78 | if(confirmado){ 79 | categorias = document.getElementsByName("categoria"); 80 | for(let i = categorias.length-1; i >= 0 ; i--){ 81 | if(categorias[i].checked){ 82 | categorias[i].parentNode.parentNode.remove(); 83 | } 84 | } 85 | categorias = document.getElementsByName("categoria"); 86 | gravaCategorias(false); 87 | } 88 | } 89 | function recuperaCategorias(){ 90 | if(!localStorage.categorias){ 91 | localStorage.setItem("categorias",lang.defaultCat); 92 | } 93 | cats = localStorage.getItem("categorias").split(";"); 94 | document.getElementById("categorias").innerHTML = ""; 95 | document.getElementById("local").options.length = 0; 96 | for(let i=0; i < cats.length; i++){ 97 | if(cats[i].length > 0){ 98 | let option = document.createElement("option"); 99 | option.text = cats[i]; 100 | option.value = cats[i]; 101 | document.getElementById("local").appendChild(option); 102 | document.getElementById("categorias").innerHTML += "
"; 106 | } 107 | } 108 | habilitaApagar("categoria","apagarcategoria"); 109 | } 110 | function n(n){ 111 | return n > 9 ? "" + n: "0" + n; 112 | } 113 | function lancaDespesa(){ 114 | data = diasemana+";"+n(dia)+"/"+n(mes)+"/"+ano+";"+n(hora)+":"+n(minuto); 115 | valor = parseFloat(document.getElementById("valor").value).toFixed(2); 116 | if(valor && !isNaN(valor)){ 117 | local = document.getElementById("local").value; 118 | localStorage.setItem(id, data+";"+valor+";"+local); 119 | localStorage.setItem("ultimoid", ++id); 120 | }else{ 121 | alerta("show",lang.moneyInvalid); 122 | return; 123 | } 124 | document.getElementById("valor").value = ""; 125 | recuperaDespesa(); 126 | } 127 | function recuperaDespesa(treina=true){ 128 | let temalguem = false; 129 | _x = new Array(); 130 | _y = new Array(); 131 | if(id > 1){ 132 | for(i = id-1; i > 0; i--){ 133 | if(localStorage.getItem(i)){ 134 | temalguem = true; 135 | break; 136 | } 137 | } 138 | } 139 | if(temalguem){ 140 | document.getElementById("apagardespesa").style.display = "block"; 141 | document.getElementById("conteudodespesas").innerHTML = ""; 142 | valormes = 0; 143 | valordia = 0; 144 | ultimomes = "00"; 145 | ultimoano = "0000"; 146 | ultimadata = "00/00/0000"; 147 | for(i = id-1; i > 0; i--){ 148 | if(localStorage.getItem(i)){ 149 | dados = localStorage.getItem(i).split(";"); 150 | splitdata = dados[1].split('/'); 151 | a = splitdata[2]; 152 | m = splitdata[1]; 153 | if(a == ano && m >= mes-3){ 154 | if(localStorage.categorias.split(';').indexOf(dados[4]) != -1){ 155 | _x.push([dados[0],dados[2].split(':')[0],parseFloat(dados[3]).toFixed(2)]); 156 | _y.push(localStorage.categorias.split(';').indexOf(dados[4])); 157 | } 158 | } 159 | if(a != ultimoano){ 160 | ultimoano = a; 161 | } 162 | if(m != ultimomes){ 163 | ultimomes = m; 164 | if( i != id-1){ 165 | document.getElementById("conteudodespesas").innerHTML += "
"; 166 | } 167 | document.getElementById("conteudodespesas").innerHTML += meses[parseInt(ultimomes)-1] 168 | +"
"; 169 | valormes = 0; 170 | } 171 | 172 | if(dados[1] != ultimadata){ 173 | ultimadata = dados[1]; 174 | document.getElementById("conteudodespesas").innerHTML += "
"+dias[parseInt(dados[0])]+", "+dados[1] 175 | +"
"; 176 | valordia = 0; 177 | } 178 | 179 | valordia += parseFloat(dados[3]); 180 | valormes += parseFloat(dados[3]); 181 | document.getElementById("conteudodespesas").innerHTML += "
" 182 | + "
" 183 | + "
" 185 | + "
"+dados[2]+"
" 186 | + "
"+money+" "+parseFloat(dados[3]).toFixed(2)+"
" 187 | + "
"+dados[4]+"
"; 188 | document.getElementById(""+ultimomes+ultimoano).innerHTML = money+" "+valormes.toFixed(2); 189 | document.getElementById(""+dados[1]).innerHTML = money+" "+valordia.toFixed(2); 190 | } 191 | } 192 | habilitaApagar("despesa","apagarbotao"); 193 | }else{ 194 | document.getElementById("conteudodespesas").innerHTML = lang.noneExpanse; 195 | document.getElementById("apagardespesa").style.display = "none"; 196 | } 197 | } 198 | function alerta(what, msg=""){ 199 | switch(what){ 200 | case "show" : 201 | document.getElementById("alertamsg").innerHTML = msg; 202 | document.getElementById("back").style.display = "block"; 203 | document.getElementById("alerta").style.display = "block"; 204 | break; 205 | case "hide" : 206 | document.getElementById("back").style.display = "none"; 207 | document.getElementById("alerta").style.display = "none"; 208 | break; 209 | } 210 | } 211 | function abreConfirma(what, callback){ 212 | switch(what){ 213 | case "show" : 214 | document.getElementById("back").style.display = "block"; 215 | document.getElementById("confirma").style.display = "block"; 216 | document.getElementById("confirmamsg").innerHTML = lang.confirm; 217 | document.getElementById("confirmabotao").onclick = function(){ 218 | callback( true ); 219 | abreConfirma('hide'); 220 | }; 221 | break; 222 | case "hide" : 223 | document.getElementById("back").style.display = "none"; 224 | document.getElementById("confirma").style.display = "none"; 225 | break; 226 | } 227 | } 228 | function habilitaApagar(checkbox,botao){ 229 | algumselecionado = false; 230 | for(i = 0; i < document.getElementsByName(checkbox).length; i++){ 231 | if(document.getElementsByName(checkbox)[i].checked){ 232 | algumselecionado = true; 233 | break; 234 | } 235 | } 236 | if(!algumselecionado){ 237 | document.getElementById(botao).disabled = true; 238 | }else{ 239 | document.getElementById(botao).disabled = false; 240 | } 241 | } 242 | function removeDespesa(confirmado=false){ 243 | abreConfirma("show",removeDespesa); 244 | if(confirmado){ 245 | let valores = document.getElementsByName("despesa"); 246 | for(i = valores.length-1; i >= 0 ; i--){ 247 | if(valores[i].checked){ 248 | localStorage.removeItem(valores[i].value); 249 | } 250 | } 251 | recuperaDespesa(); 252 | } 253 | } 254 | function navegacao(aba){ 255 | abas = document.getElementsByClassName("aba"); 256 | for(i = 0; i < abas.length; i++){ 257 | abas[i].style.display = "none"; 258 | } 259 | document.getElementById(aba).style.display = "block"; 260 | } 261 | 262 | function resetar(confirmado=false){ 263 | abreConfirma("show", resetar); 264 | if(confirmado){ 265 | localStorage.clear(); 266 | init(); 267 | } 268 | } 269 | function predizCategoria(){ 270 | if(_x && _x.length > 0){ 271 | let textInput = document.getElementById('valor'); 272 | let hora = new Date().getHours(); 273 | 274 | let timeout = null; 275 | 276 | clearTimeout(timeout); 277 | timeout = setTimeout(function () { 278 | document.getElementById("local").selectedIndex = previsao([diasemana,hora,textInput.value]); 279 | }, 500); 280 | } 281 | } 282 | function previsao(_p,k=3){ 283 | let distancia = [], 284 | resposta, 285 | compare, 286 | r = {}, 287 | resultado, 288 | _d; 289 | for(i=0; i < _x.length; i++){ 290 | _d = 0; 291 | for(j=0; j < _x[i].length; j++){ 292 | _d += Math.abs( parseFloat(_p[j]) - parseFloat(_x[i][j]) ); 293 | } 294 | distancia.push( _d ); 295 | } 296 | resultado = []; 297 | if(distancia.length >= k*k){ 298 | for(i=0; i < k; i++){ 299 | let iDist = distancia.indexOf(Math.min.apply( null, distancia )); 300 | resultado.push( _y[iDist] ); 301 | distancia.splice( iDist,1 ); 302 | } 303 | r = {}; 304 | compare = -1; 305 | for(i=0; i < k; i++){ 306 | if( !r[resultado[i]] ){ 307 | r[resultado[i]] = 1; 308 | }else{ 309 | r[resultado[i]] += 1 310 | } 311 | if( r[resultado[i]] > compare ){ 312 | compare = r[resultado[i]]; 313 | resposta = resultado[i]; 314 | } 315 | } 316 | }else{ 317 | resposta = _y[distancia.indexOf(Math.min.apply( null, distancia ))]; 318 | } 319 | return resposta; 320 | } 321 | window.onload = () => { 322 | window.applicationCache.addEventListener('updateready', function(e) { 323 | if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { 324 | window.applicationCache.swapCache(); 325 | window.location.reload(); 326 | } 327 | }, false); 328 | fetch(langSetting) 329 | .then( response => { 330 | response.json().then( result => { 331 | lang = result; 332 | init(); 333 | }); 334 | }).catch( e => console.error( e ) ); 335 | }; 336 | -------------------------------------------------------------------------------- /js/pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pt-br", 3 | "menu" : { 4 | "expense": "Despesas", 5 | "new": "Novo", 6 | "settings" : "Configurações", 7 | "categories" : "Categorias", 8 | "category" : "Category", 9 | "reset" : "Resetar", 10 | "erase" : "Apagar" 11 | }, 12 | "day" : [ 13 | "Domingo", 14 | "Segunda", 15 | "Terça", 16 | "Quarta", 17 | "Quinta", 18 | "Sexta", 19 | "Sábado" 20 | ], 21 | "month" : [ 22 | "Janeiro", 23 | "Fevereiro", 24 | "Março", 25 | "Abril", 26 | "Maio", 27 | "Junho", 28 | "Julho", 29 | "Agosto", 30 | "Setembro", 31 | "Outurbo", 32 | "Novembro", 33 | "Dezembro" 34 | ], 35 | "money" : "R$", 36 | "catExists" : "Categoria já existe!", 37 | "invalidData" : "Dados inválidos!", 38 | "defaultCat" : "Mercado;Posto;Lanche;Gasolina", 39 | "moneyInvalid" : "Valor inválido! ex. válido: 105.50", 40 | "noneExpanse" : "Nenhuma despesa lançada!", 41 | "reset" : "Resetado!", 42 | "confirm" : "Confirma?", 43 | "accept" : "Aceitar", 44 | "cancel" : "Cancel" 45 | } 46 | -------------------------------------------------------------------------------- /js/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "zh-cn", 3 | "menu" : { 4 | "expense": "消费", 5 | "newExpense" : "添加", 6 | "settings" : "设置", 7 | "categories" : "分类", 8 | "category" : "分类", 9 | "reset" : "重置", 10 | "erase" : "删除" 11 | }, 12 | "day" : [ "星期天", 13 | "星期一", 14 | "星期二", 15 | "星期三", 16 | "星期四", 17 | "星期五", 18 | "星期六" ], 19 | "month" : [ "一月", 20 | "二月", 21 | "三月", 22 | "四月", 23 | "五月", 24 | "六月", 25 | "七月", 26 | "八月", 27 | "九月", 28 | "十月", 29 | "十一月", 30 | "十二月" ], 31 | "money" : "RMB", 32 | "catExists" : "分类已存在!", 33 | "invalidData" : "输入错误", 34 | "defaultCat" : "市场;汽油站;零食;商店", 35 | "moneyInvalid" : "输入错误!正确例子:105.50", 36 | "noneExpanse" : "无消费!", 37 | "reset" : "重置了!", 38 | "confirm" : "确定?", 39 | "accept" : "接受", 40 | "cancel" : "取消" 41 | } 42 | -------------------------------------------------------------------------------- /manifest.appcache: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # 2018-10-15:v1.1 3 | 4 | CACHE: 5 | manifest.json 6 | favicon.png 7 | index.htm 8 | css/main.css 9 | js/main.js 10 | js/pt-br.json 11 | js/en.json 12 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Budget", 3 | "short_name": "Budget", 4 | "description": "A simply budgeting app that makes predictions", 5 | "start_url": "index.htm", 6 | "display": "standalone", 7 | "orientation": "portrait", 8 | "icons": [ 9 | { 10 | "src": "favicon.png", 11 | "sizes": "128x128", 12 | "type": "image/png", 13 | "density": 1 14 | } 15 | ] 16 | } 17 | --------------------------------------------------------------------------------