The difference is not about declaring types on variables. It's a bit more subtle than that (and pace Eric Lippert, i think the term is reasonably well-defined). The distinction is that in a strongly-typed language, every expression has a type which can be determined at compile time, and only operations appropriate to that type are allowed.
In an untyped ("weakly typed" to critics, "dynamically typed" to fans) language, that is not the case. The language allows any operation to be performed on any type, with the rather substantial proviso that the operation may fail. That is, while the language may allow the operation, the runtime may not.
Note that it's possible to have a strongly-typed language without requiring type declarations everywhere. Indeed, no strongly-typed language does. Consider this bit of Java:
String s = "hellO";
int l = s.getBytes().length;
How does the compiler decide that .length
is legal there? It's legal because it's being used on a byte[]
. But there is no declaration of anything as being a byte[]
here. Rather, the compiler knows that s
is a String
, and that when you call getBytes()
on a String
, you get a byte[]
. It infers from those facts that the type of s.getBytes()
is a byte[]
, and so that it is legal to ask for its length
.
Some languages whose type systems are more sophisticated than Java's allow the compiler to infer more than this. For example, in Scala, you can say:
val s = "hello"
val l = s.getBytes().length
And the compiler will infer the types of s
and l
, as well as of the intermediate expressions.
Languages which have strong typing but artificial limits on type inference which require redundant type declarations (like Java) are described as having manifest typing, because the types must be made manifest, which is a fancy way of saying explicitly brought into existence, which is a fancy way of saying written down.